
前言
- 習(xí)慣用
Json、XML數(shù)據(jù)存儲格式的你們,相信大多都沒聽過Protocol Buffer -
Protocol Buffer其實(shí) 是Google出品的一種輕量 & 高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,性能比Json、XML真的強(qiáng)!太!多!
由于
Protocol Buffer已經(jīng)具備足夠的吸引力
- 今天,我將講解為什么
Protocol Buffer的性能如此的好:
a. 序列化速度 & 反序列化速度快
b. 數(shù)據(jù)壓縮效果好,即序列化后的數(shù)據(jù)量體積小
Carson帶你學(xué)序列化Protocol Buffer系列文章
快來看看Google出品的Protocol Buffer,別只會用Json和XML了
Carson帶你學(xué)序列化:手把手教你如何安裝Protocol Buffer
Carson帶你學(xué)序列化:全面詳解ProtocolBuffer語法
Carson帶你學(xué)序列化:Google出品的序列化神器Protocol Buffer使用指南
Carson帶你學(xué)序列化:Protocol Buffer序列化原理大揭秘-為什么性能這么好?
Carson帶你學(xué)序列化:深入源碼分析Protocol Buffer
Carson帶你學(xué)序列化:深入分析JSON多種解析方式(Gson、AS自帶org.json、Jackson)
Carson帶你學(xué)序列化:深入分析XML多種解析方式(DOM、SAX、PULL)
目錄

1. 定義
一種 結(jié)構(gòu)化數(shù)據(jù) 的數(shù)據(jù)存儲格式(類似于 XML、Json )
Protocol Buffer目前有兩個版本:proto2和proto3- 因?yàn)?code>proto3 還是beta 版,所以本次講解是
proto2
2. 作用
通過將 結(jié)構(gòu)化的數(shù)據(jù) 進(jìn)行 串行化(序列化),從而實(shí)現(xiàn) 數(shù)據(jù)存儲 / RPC 數(shù)據(jù)交換的功能
- 序列化: 將 數(shù)據(jù)結(jié)構(gòu)或?qū)ο?轉(zhuǎn)換成 二進(jìn)制串 的過程
- 反序列化:將在序列化過程中所生成的二進(jìn)制串 轉(zhuǎn)換成 數(shù)據(jù)結(jié)構(gòu)或者對象 的過程
3. 特點(diǎn)
- 對比于 常見的
XML、Json數(shù)據(jù)存儲格式,Protocol Buffer有如下特點(diǎn):

4. 應(yīng)用場景
傳輸數(shù)據(jù)量大 & 網(wǎng)絡(luò)環(huán)境不穩(wěn)定 的數(shù)據(jù)存儲、RPC 數(shù)據(jù)交換 的需求場景
如 即時IM (QQ、微信)的需求場景
總結(jié)
在 傳輸數(shù)據(jù)量較大的需求場景下,Protocol Buffer比XML、Json 更小、更快、使用 & 維護(hù)更簡單!
5. 使用流程
關(guān)于 Protocol Buffer 的使用流程,具體請看我寫的文章:快來看看Google出品的Protocol Buffer,別只會用Json和XML了
6. 知識基礎(chǔ)
6.1 網(wǎng)絡(luò)通信協(xié)議
- 序列化 & 反序列化 屬于通訊協(xié)議的一部分
- 通訊協(xié)議采用分層模型:
TCP/IP模型(四層) &OSI模型 (七層)

-
序列化 / 反序列化 屬于
TCP/IP模型 應(yīng)用層 和 OSI`模型 展示層的主要功能:- (序列化)把 應(yīng)用層的對象 轉(zhuǎn)換成 二進(jìn)制串
- (反序列化)把 二進(jìn)制串 轉(zhuǎn)換成 應(yīng)用層的對象
所以,
Protocol Buffer屬于TCP/IP模型的應(yīng)用層 &OSI模型的展示層
6.2 數(shù)據(jù)結(jié)構(gòu)、對象與二進(jìn)制串
不同的計算機(jī)語言中,數(shù)據(jù)結(jié)構(gòu),對象以及二進(jìn)制串的表示方式并不相同。
a. 對于數(shù)據(jù)結(jié)構(gòu)和對象
對于面向?qū)ο蟮恼Z言(如
Java):對象 =Object= 類的實(shí)例化;在Java中最接近數(shù)據(jù)結(jié)構(gòu) 即POJO(Plain Old Java Object),或Javabean(只有setter/getter方法的類)對于半面向?qū)ο蟮恼Z言(如
C++),對象 =class,數(shù)據(jù)結(jié)構(gòu) =struct
b. 二進(jìn)制串
- 對于
C++,因?yàn)榫哂袃?nèi)存操作符,所以 二進(jìn)制串 容易理解:C++的字符串可以直接被傳輸層使用,因?yàn)槠浔举|(zhì)上就是以'\0'結(jié)尾的存儲在內(nèi)存中的二進(jìn)制串 - 對于
Java,二進(jìn)制串 = 字節(jié)數(shù)組 =byte[]
byte屬于Java的八種基本數(shù)據(jù)類型- 二進(jìn)制串 容易和
String混淆:String一種特殊對象(Object)。對于跨語言間的通訊,序列化后的數(shù)據(jù)當(dāng)然不能是某種語言的特殊數(shù)據(jù)類型。
6.3 T - L - V 的數(shù)據(jù)存儲方式
- 定義
即Tag - Length - Value,標(biāo)識 - 長度 - 字段值 存儲方式
- 作用
以 標(biāo)識 - 長度 - 字段值 表示單個數(shù)據(jù),最終將所有數(shù)據(jù)拼接成一個 字節(jié)流,從而 實(shí)現(xiàn) 數(shù)據(jù)存儲 的功能
其中
Length可選存儲,如 儲存Varint編碼數(shù)據(jù)就不需要存儲Length
- 示意圖

- 優(yōu)點(diǎn)
從上圖可知,T - L - V存儲方式的優(yōu)點(diǎn)是- 不需要分隔符 就能 分隔開字段,減少了 分隔符 的使用
- 各字段 存儲得非常緊湊,存儲空間利用率非常高
- 若 字段沒有被設(shè)置字段值,那么該字段在序列化時的數(shù)據(jù)中是完全不存在的,即不需要編碼
相應(yīng)字段在解碼時才會被設(shè)置為默認(rèn)值
7. 序列化原理解析
請記住Protocol Buffer的 三個關(guān)于數(shù)據(jù)存儲 的重要結(jié)論:
- 結(jié)論1
Protocol Buffer將 消息里的每個字段 進(jìn)行編碼后,再利用T - L - V存儲方式 進(jìn)行數(shù)據(jù)的存儲,最終得到的是一個 二進(jìn)制字節(jié)流
序列化 = 對數(shù)據(jù)進(jìn)行編碼 + 存儲
- 結(jié)論2
Protocol Buffer對于不同數(shù)據(jù)類型 采用不同的 序列化方式(編碼方式 & 數(shù)據(jù)存儲方式),如下圖:
數(shù)據(jù)類型 對應(yīng)的編碼方式
從上表可以看出:
- 對于存儲
Varint編碼數(shù)據(jù),就不需要存儲字節(jié)長度Length,所以實(shí)際上Protocol Buffer的存儲方式是T - V; - 若
Protocol Buffer采用其他編碼方式(如LENGTH_DELIMITED)則采用T - L - V
- 結(jié)論3:因?yàn)?
Protocol Buffer對于數(shù)據(jù)字段值的 獨(dú)特編碼方式 &T - L - V數(shù)據(jù)存儲方式,使得Protocol Buffer序列化后數(shù)據(jù)量體積如此小
下面,我將對不同的編碼方式 & 數(shù)據(jù)存儲方式進(jìn)行逐一講解。
7.1 Wire Type = 0時的編碼 & 數(shù)據(jù)存儲方式

7.1.1 編碼方式: Varint & Zigzag
A. Varint編碼方式介紹
i. 簡介
- 定義:一種變長的編碼方式
- 原理:用字節(jié) 表示 數(shù)字:值越小的數(shù)字,使用越少的字節(jié)數(shù)表示
- 作用:通過減少 表示數(shù)字 的字節(jié)數(shù) 從而進(jìn)行數(shù)據(jù)壓縮
如:
- 對于
int32類型的數(shù)字,一般需要 4個字節(jié) 表示;
- 若采用
Varint編碼,對于很小的int32類型 數(shù)字,則可以用 1個字節(jié) 來表示- 雖然大的數(shù)字會需要 5 個 字節(jié) 來表示,但大多數(shù)情況下,消息都不會有很大的數(shù)字,所以采用
Varint方法總是可以用更少的字節(jié)數(shù)來表示數(shù)字
ii. 原理介紹
- 源碼分析
private void writeVarint32(int n) {
int idx = 0;
while (true) {
if ((n & ~0x7F) == 0) {
i32buf[idx++] = (byte)n;
break;
} else {
i32buf[idx++] = (byte)((n & 0x7F) | 0x80);
// 步驟1:取出字節(jié)串末7位
// 對于上述取出的7位:在最高位添加1構(gòu)成一個字節(jié)
// 如果是最后一次取出,則在最高位添加0構(gòu)成1個字節(jié)
n >>>= 7;
// 步驟2:通過將字節(jié)串整體往右移7位,繼續(xù)從字節(jié)串的末尾選取7位,直到取完為止。
}
}
trans_.write(i32buf, 0, idx);
// 步驟3: 將上述形成的每個字節(jié) 按序拼接 成一個字節(jié)串
// 即該字節(jié)串就是經(jīng)過Varint編碼后的字節(jié)
}
從上面可看出:Varint 中每個 字節(jié) 的最高位 都有特殊含義:
- 如果是 1,表示后續(xù)的 字節(jié) 也是該數(shù)字的一部分
- 如果是 0,表示這是最后一個字節(jié),且剩余 7位 都用來表示數(shù)字
所以,當(dāng)使用
Varint解碼時時,只要讀取到最高位為0的字節(jié)時,就表示已經(jīng)是Varint的最后一個字節(jié)
因此:
- 小于 128 的數(shù)字 都可以用 1個字節(jié) 表示;
- 大于 128 的數(shù)字,比如 300,會用兩個字節(jié)來表示:10101100 00000010
下面,我將用兩個個例子來說明Varint編碼方式的使用
- 目的:對 數(shù)據(jù)類型為Int32 的 字段值為296 和字段值為104 進(jìn)行
Varint編碼 - 以下是編碼過程

從上面可以看出:
- 對于 int32 類型的數(shù)字,一般需要 4 個字節(jié) 來表示;
- 但采用
Varint方法,對于很小的Int32類型 數(shù)字(小于256),則可以用 1個字節(jié) 來表示;
以此類推,比如300也只需要2個字節(jié)
- 雖然大的數(shù)字會需要 5 個字節(jié) 來表示,但大多數(shù)情況下,消息都不會有很大的數(shù)字
- 所以采用
Varint方法總是可以用更少的字節(jié)數(shù)來表示數(shù)字,從而更好地實(shí)現(xiàn)數(shù)據(jù)壓縮
下面繼續(xù)看如何解析經(jīng)過Varint 編碼的字節(jié)

Varint 編碼方式的不足
- 背景:在計算機(jī)內(nèi),負(fù)數(shù)一般會被表示為很大的整數(shù)
因?yàn)橛嬎銠C(jī)定義負(fù)數(shù)的符號位為數(shù)字的最高位
- 問題:如果采用
Varint編碼方式 表示一個負(fù)數(shù),那么一定需要 5 個 byte(因?yàn)樨?fù)數(shù)的最高位是1,會被當(dāng)做很大的整數(shù)去處理) - 解決方案:
Protocol Buffer定義了sint32 / sint64類型表示負(fù)數(shù),通過先采用Zigzag編碼(將 有符號數(shù) 轉(zhuǎn)換成 無符號數(shù)),再采用Varint編碼,從而用于減少編碼后的字節(jié)數(shù)
表示負(fù)數(shù)時采用Zigzag編碼
- 對于
int32 / int64類型的字段值(正數(shù)),Protocol Buffer直接采用Varint編碼- 對于
sint32 / sint64類型的字段值(負(fù)數(shù)),Protocol Buffer會先采用Zigzag編碼,再采用Varint編碼
- 總結(jié):為了更好地減少 表示負(fù)數(shù)時 的字節(jié)數(shù),
Protocol Buffer在Varint編碼上又增加了Zigzag編碼方式進(jìn)行編碼 - 下面將詳細(xì)介紹
Zigzag編碼方式
B. Zigzag編碼方式詳解
i. 簡介
- 定義:一種變長的編碼方式
- 原理:使用 無符號數(shù) 來表示 有符號數(shù)字;
- 作用:使得絕對值小的數(shù)字都可以采用較少 字節(jié) 來表示;
特別是對 表示負(fù)數(shù)的數(shù)據(jù) 能更好地進(jìn)行數(shù)據(jù)壓縮
b. 原理
- 源碼分析
public int int_to_zigzag(int n)
// 傳入的參數(shù)n = 傳入字段值的二進(jìn)制表示(此處以負(fù)數(shù)為例)
// 負(fù)數(shù)的二進(jìn)制 = 符號位為1,剩余的位數(shù)為 該數(shù)絕對值的原碼按位取反;然后整個二進(jìn)制數(shù)+1
{
return (n <<1) ^ (n >>31);
// 對于sint 32 數(shù)據(jù)類型,使用Zigzag編碼過程如下:
// 1. 將二進(jìn)制表示數(shù) 左移1位(左移 = 整個二進(jìn)制左移,低位補(bǔ)0)
// 2. 將二進(jìn)制表示數(shù) 右移31位
// 對于右移:
// 首位是1的二進(jìn)制(有符號數(shù)),是算數(shù)右移,即右移后左邊補(bǔ)1
// 首位是0的二進(jìn)制(無符號數(shù)),是邏輯左移,即右移后左邊補(bǔ)0
// 3. 將上述二者進(jìn)行異或
// 對于sint 64 數(shù)據(jù)類型 則為: return (n << 1> ^ (n >> 63) ;
}
// 附:將Zigzag值解碼為整型值
public int zigzag_to_int(int n)
{
return(n >>> 1) ^ -(n & 1);
// 右移時,需要用不帶符號的移動,否則如果第一位數(shù)據(jù)位是1的話,就會補(bǔ)1
}
- 實(shí)例說明:將
-2進(jìn)行Zigzag編碼:

-
Zigzag編碼 是補(bǔ)充Varint編碼在 表示負(fù)數(shù) 的不足,從而更好的幫助Protocol Buffer進(jìn)行數(shù)據(jù)的壓縮 - 所以,如果提前預(yù)知字段值是可能取負(fù)數(shù)的時候,記得采用
sint32 / sint64數(shù)據(jù)類型
總結(jié)
Protocol Buffer 通過Varint和Zigzag編碼后大大減少了字段值占用字節(jié)數(shù)。
7.1.2 存儲方式:T - V
- 消息字段的標(biāo)識號、數(shù)據(jù)類型 & 字段值經(jīng)過
Protocol Buffer采用Varint&Zigzag編碼后,以T - V方式進(jìn)行數(shù)據(jù)存儲
對于
Varint&Zigzag編碼,省略了T - L - V中的字節(jié)長度Length

下面將詳細(xì)介紹T - V 存儲方式中的存儲細(xì)節(jié):Tag & Value
1. Tag
- 定義:經(jīng)過
Protocol Buffer采用Varint&Zigzag編碼后 的消息字段 標(biāo)識號 & 數(shù)據(jù)類型 的值 - 作用:標(biāo)識 字段
- 存儲了字段的標(biāo)識號(
field_number)和 數(shù)據(jù)類型(wire_type),即Tag= 字段數(shù)據(jù)類型(wire_type) + 標(biāo)識號(field_number)- 占用 一個字節(jié) 的長度(如果標(biāo)識號超過了16,則占用多一個字節(jié)的位置)
- 解包時,
Protocol Buffer根據(jù)Tag將Value對應(yīng)于消息中的 字段
- 具體使用
// Tag 的具體表達(dá)式如下
Tag = (field_number << 3) | wire_type
// 參數(shù)說明:
// field_number:對應(yīng)于 .proto文件中消息字段的標(biāo)識號,表示這是消息里的第幾個字段
// field_number << 3:表示 field_number = 將 Tag的二進(jìn)制表示 右移三位 后的值
// field_num左移3位不會導(dǎo)致數(shù)據(jù)丟失,因?yàn)楸硎痉秶€是足夠大地去表示消息里的字段數(shù)目
// wire_type:表示 字段 的數(shù)據(jù)類型
// wire_type = Tag的二進(jìn)制表示 的最低三位值
// wire_type的取值
enum WireType {
WIRETYPE_VARINT = 0,
WIRETYPE_FIXED64 = 1,
WIRETYPE_LENGTH_DELIMITED = 2,
WIRETYPE_START_GROUP = 3,
WIRETYPE_END_GROUP = 4,
WIRETYPE_FIXED32 = 5
};
// 從上面可以看出,`wire_type`最多占用 3位 的內(nèi)存空間(因?yàn)?3位 足以表示 0-5 的二進(jìn)制)
// 以下是 wire_type 對應(yīng)的 數(shù)據(jù)類型 表

- 實(shí)例說明
// 消息對象
message person
{
required int32 id = 1;
// wire type = 0,field_number =1
required string name = 2;
// wire type = 2,field_number =2
}
// 如果一個Tag的二進(jìn)制 = 0001 0010
標(biāo)識號 = field_number = field_number << 3 =右移3位 = 0000 0010 = 2
數(shù)據(jù)類型 = wire_type = 最低三位表示 = 010 = 2
2. Value
經(jīng)過 Protocol Buffer采用Varint & Zigzag編碼后 的消息字段的值
7.1.3 實(shí)例說明
下面通過一個實(shí)例進(jìn)行整個編碼過程的說明:
- 消息說明
message Test
{
required int32 id1 = 1;
required int32 id2 = 2;
}
// 在代碼中給id1 附上1個字段值:296
// 在代碼中給id2 附上1個字段值:296
Test.setId1(300);
Test.setId2(296);
// 編碼結(jié)果為:二進(jìn)制字節(jié)流 = [8,-84,2,16, -88, 2]
- 整個編碼過程如下

7.2 Wire Type = 1& 5時的編碼&數(shù)據(jù)存儲方式

-
64(32)-bit編碼方式較簡單:編碼后的數(shù)據(jù)具備固定大小 = 64位(8字節(jié)) / 32位(4字節(jié))
兩種情況下,都是高位在后,低位在前
- 采用
T - V方式進(jìn)行數(shù)據(jù)存儲,同上。
7.3 Wire Type = 2時的 編碼 & 數(shù)據(jù)存儲方式

- 對于編碼方式:

- 數(shù)據(jù)存儲方式:
T - L - V

此處主要講解三種數(shù)據(jù)類型:
-
String類型 - 嵌套消息類型(
Message) - 通過
packed修飾的repeat字段(即packed repeated fields)
1. String類型
字段值(即V) 采用UTF-8編碼

- 例子:
message Test2
{
required string str = 2;
}
// 將str設(shè)置為:testing
Test2.setStr(“testing”)
// 經(jīng)過protobuf編碼序列化后的數(shù)據(jù)以二進(jìn)制的方式輸出
// 輸出為:18, 7, 116, 101, 115, 116, 105, 110, 103

2. 嵌套消息類型(Message)
- 存儲方式:
T - L - V
- 外部消息的
V即為 嵌套消息的字段- 在
T - L -V里嵌套了一系列的T - L -V
- 編碼方式:字段值(即
V) 根據(jù)字段的數(shù)據(jù)類型采用不同編碼方式

- 實(shí)例
定義如下嵌套消息:
message Test2
{
required string str = 1;
required int32 id1 = 2;
}
message Test3 {
required Test2 c = 1;
}
// 將Test2中的字段str設(shè)置為:testing
// 將Test2中的字段id1設(shè)置為:296
// 編碼后的字節(jié)為:10 ,12 ,18,7,116, 101, 115, 116, 105, 110, 103,16,-88,2
編碼 & 存儲方式如下

3. 通過packed修飾的 repeat 字段
repeated 修飾的字段有兩種表達(dá)方式:
message Test
{
repeated int32 Car = 4 ;
// 表達(dá)方式1:不帶packed=true
repeated int32 Car = 4 [packed=true];
// 表達(dá)方式2:帶packed=true
// proto 2.1 開始可使用
// 區(qū)別在于:是否連續(xù)存儲repeated類型數(shù)據(jù)
}
// 在代碼中給`repeated int32 Car`附上3個字段值:3、270、86942
Test.setCar(3);
Test.setCar(270);
Test.setCar(86942);
- 背景:對于同一個
repeated字段、多個字段值來說,他們的Tag都是相同的,即數(shù)據(jù)類型 & 標(biāo)識號都相同
repeated類型可以看成是數(shù)組
- 問題:若以傳統(tǒng)的多個 T - V對存儲(不帶
packed=true),則會導(dǎo)致Tag的冗余,即相同的Tag存儲多次;

- 解決方案:采用帶
packed=true的repeated字段存儲方式,即將相同的Tag只存儲一次、添加repeated字段下所有字段值的長度Length、連續(xù)存儲repeated字段值,組成一個大的Tag - Length - Value -Value -Value對,即T - L - V - V - V對。

通過采用帶packed=true 的 repeated 字段存儲方式,從而更好地壓縮序列化后的數(shù)據(jù)長度。
特別注意
-
Protocol Buffer的packed修飾只用于repeated字段 或 基本類型的repeated字段 - 用在其他字段,編譯
.proto文件時會報錯
8. 特別注意
- 注意1:若
required字段沒有被設(shè)置字段值,那么在IsInitialized()進(jìn)行初始化檢查會報錯并提示失敗
所以
required字段必須要被設(shè)置字段值
- 注意2:序列化順序 是根據(jù)
Tag標(biāo)識號 從小到大 進(jìn)行編碼
和
.proto文件內(nèi) 字段定義的數(shù)據(jù)無關(guān)
- 注意3:
T - V的數(shù)據(jù)存儲方式保證了Protobuf的版本兼容:高<->低 或 低<->高都可以適配
若新版本 增加了
required字段, 舊版本 在數(shù)據(jù)解碼時會認(rèn)為IsInitialized()失敗,所以慎用required字段
9. 使用建議
根據(jù)上面的序列化原理分析,我總結(jié)出以下Protocol Buffer的使用建議
通過下面建議能有效降低序列化后數(shù)據(jù)量的大小
-
建議1:多用
optional或repeated修飾符
因?yàn)槿?code>optional 或repeated字段沒有被設(shè)置字段值,那么該字段在序列化時的數(shù)據(jù)中是完全不存在的,即不需要進(jìn)行編碼
相應(yīng)的字段在解碼時才會被設(shè)置為默認(rèn)值
建議2:字段標(biāo)識號(
Field_Number)盡量只使用 1-15,且不要跳動使用
因?yàn)?code>Tag里的Field_Number是需要占字節(jié)空間的。如果Field_Number>16時,Field_Number的編碼就會占用2個字節(jié),那么Tag在編碼時也就會占用更多的字節(jié);如果將字段標(biāo)識號定義為連續(xù)遞增的數(shù)值,將獲得更好的編碼和解碼性能建議3:若需要使用的字段值出現(xiàn)負(fù)數(shù),請使用
sint32 / sint64,不要使用int32 / int64
因?yàn)椴捎?code>sint32 / sint64數(shù)據(jù)類型表示負(fù)數(shù)時,會先采用Zigzag編碼再采用Varint編碼,從而更加有效壓縮數(shù)據(jù)建議4:對于
repeated字段,盡量增加packed=true修飾
因?yàn)榧恿?code>packed=true修飾repeated字段采用連續(xù)數(shù)據(jù)存儲方式,即T - L - V - V -V方式
10. 序列化 & 反序列化過程
-
Protocol Buffer除了序列化 & 反序列化后的數(shù)據(jù)體積小,序列化 & 反序列化的速度也非常快 - 下面我將講解序列化 & 反序列化的序列化過程
10.1 Protocol Buffer的序列化 & 反序列化過程
序列化過程如下:
- 判斷每個字段是否有設(shè)置值,有值才進(jìn)行編碼
- 根據(jù) 字段標(biāo)識號&數(shù)據(jù)類型 將 字段值 通過不同的編碼方式進(jìn)行編碼
由于:
a. 編碼方式簡單(只需要簡單的數(shù)學(xué)運(yùn)算 = 位移等等)
b. 采用 Protocol Buffer 自身的框架代碼 和 編譯器 共同完成
所以Protocol Buffer的序列化速度非???。
反序列化過程如下:
- 調(diào)用 消息類的
parseFrom(input)解析從輸入流讀入的二進(jìn)制字節(jié)數(shù)據(jù)流
從上面可知,
Protocol Buffer解析過程只需要通過簡單的解碼方式即可完成,無需復(fù)雜的詞法語法分析,因此 解析速度非???。
- 將解析出來的數(shù)據(jù) 按照指定的格式讀取到
Java、C++、Phyton對應(yīng)的結(jié)構(gòu)類型中
由于:
a. 解碼方式簡單(只需要簡單的數(shù)學(xué)運(yùn)算 = 位移等等)
b. 采用 Protocol Buffer 自身的框架代碼 和 編譯器 共同完成
所以Protocol Buffer的反序列化速度非???。
10.2 對比于XML 的序列化 & 反序列化過程
XML的反序列化過程如下:
- 從文件中讀取出字符串
- 將字符串轉(zhuǎn)換為
XML文檔對象結(jié)構(gòu)模型 - 從
XML文檔對象結(jié)構(gòu)模型中讀取指定節(jié)點(diǎn)的字符串 - 將該字符串轉(zhuǎn)換成指定類型的變量
上述過程非常復(fù)雜,其中,將 XML 文件轉(zhuǎn)換為文檔對象結(jié)構(gòu)模型的過程通常需要完成詞法文法分析等大量消耗 CPU 的復(fù)雜計算。
因?yàn)樾蛄谢?& 反序列化過程簡單,所以序列化 & 反序列化過程速度非???,這也是 Protocol Buffer效率高的原因
11.總結(jié)
Protocol Buffer的性能好,主要體現(xiàn)在 序列化后的數(shù)據(jù)體積小 & 序列化速度快,最終使得傳輸效率高,其原因如下:
序列化速度快的原因:
a. 編碼 / 解碼 方式簡單(只需要簡單的數(shù)學(xué)運(yùn)算 = 位移等等)
b. 采用Protocol Buffer自身的框架代碼 和 編譯器 共同完成序列化后的數(shù)據(jù)量體積小(即數(shù)據(jù)壓縮效果好)的原因:
a. 采用了獨(dú)特的編碼方式,如Varint、Zigzag編碼方式等等
b. 采用T - L - V的數(shù)據(jù)存儲方式:減少了分隔符的使用 & 數(shù)據(jù)存儲得緊湊Carson帶你學(xué)序列化Protocol Buffer系列文章
快來看看Google出品的Protocol Buffer,別只會用Json和XML了
Carson帶你學(xué)序列化:手把手教你如何安裝Protocol Buffer
Carson帶你學(xué)序列化:全面詳解ProtocolBuffer語法
Carson帶你學(xué)序列化:Google出品的序列化神器Protocol Buffer使用指南
Carson帶你學(xué)序列化:Protocol Buffer序列化原理大揭秘-為什么性能這么好?
Carson帶你學(xué)序列化:深入源碼分析Protocol Buffer
Carson帶你學(xué)序列化:深入分析JSON多種解析方式(Gson、AS自帶org.json、Jackson)
Carson帶你學(xué)序列化:深入分析XML多種解析方式(DOM、SAX、PULL)
歡迎關(guān)注Carson_Ho的簡書
不定期分享關(guān)于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。


