原文:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/encoding.html
看一個簡單的mesage:
message Test1 {
required int32 a = 1;
}
如果應用程式建立了一個Test1的對象,並把a賦值為150,那麼protobuf會把它編碼成這樣三個位元組:08 96 01。
這三個位元組代表什麼含義呢?
Base 128 Varints
protobuf使用一種叫做“Base 128 Varints”的編碼方式,它使用一個或者多個位元組來序列化一個整數,數字越小使用的位元組數就越少。
具體來說就是:
除了最後一個位元組之外的每一個位元組的最高位置1,代表還沒結束,後面還有更多的位元組,最後一個位元組的最高位置0,表明結束,
每個位元組剩下的7位,是以二進位的補碼,低位元組序( least significant group first)的形式表示。
以數字1為例,二進位是0000 0001,最高位是0代表後面沒有更多的位元組,剩下的7位代表1。
以數字300為例,它在“Base 128 Varints”規則下的表示形式是:1010 1100 0000 0010
第一個位元組是 1010 1100,最高位是1,代表後面還有更多的位元組,因此,第一個位元組的內容是 010 1100
第二個位元組是 0000 0010,最高位是0,代表後面沒有更多的位元組,因此,第二個位元組的內容是 000 0010
因為是採用“低位元組序”因此,實際的位元組是:000 0010 010 1100 = 1 0010 1100 = 256 + 32 + 8 + 4 = 300
message的結構
protobuf的message是一連串的key/value對,二進位形式的message也是用key/value的形式,只不過,二進位形式的message的key是message裡面的序號,value是message裡面key和value的組合。接收端只有通過參考.proto才可以正確的解碼。
當message被編碼的時候,key和value一塊寫到二進位流裡面,解碼的時候,解析器能夠跳過那些他不認識的field,用這種方式,新的filed可以添加到那些舊的不認識這些欄位的程式的message裡面,二進位形式的key實際上有兩個部分組成,一部分是.proto檔案裡面的序號,另一部分是傳輸類型(wire type)。
傳輸類型:
Type|Meaning | Used For
-----------------------------------------------------------------------------------
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum
-----------------------------------------------------------------------------------
1 | 64-bit | fixed64, sfixed64, double
-----------------------------------------------------------------------------------
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields
-----------------------------------------------------------------------------------
3 | Start group | groups (deprecated)
-----------------------------------------------------------------------------------
4 | End group | groups (deprecated)
-----------------------------------------------------------------------------------
5 | 32-bit | fixed32, sfixed32, float
-----------------------------------------------------------------------------------
二進位的key都是varint,值是field_number << 3) | wire_type,也就是說,後三位儲存的是傳輸類型,前5位儲存的是序號。
下面我們來看一下最開始的那個例子:08 96 01,因為在二進位流裡面的第一個數字都是key,因此key就是08,二進位:0000 1000,
後三位是000,代表傳輸類型是0,前面的5位是0000 1就是數字1,代表在.proto檔案裡面的序號是1.
因此,通過傳輸類型知道後面傳輸的是Varint,通過序號知道在message裡面的tag是1.
使用Base 128 Varints的解碼方式,解碼96 01,二進位:1001 0110 0000 0001
第一個位元組是 1001 0110,最高位是1,代表後面還有更多的位元組,因此,第一個位元組的內容是 001 0110
第一個位元組是 0000 0001,最高位是0,代表後面沒有更多的位元組,因此,第二個位元組的內容是 000 0001
因為是採用“低位元組序”因此,實際的位元組是:000 0001 001 0110 = 1001 0110 = 128 + 16 + 4 + 2 = 150
更多的值的類型
有符號的整數
使用Base 128 Varints方式編碼負數的時候,有符號的sint32,sint63 跟 標準的int32,int64有很大的不同。當負數用int32,int64表示的時候,varint編碼以後總是佔10個位元組的長度,它是把這個負數當成了一個很大的正數來對待的,如果用sint32,sint64來表示的話,結果會用更高效的ZigZag方式進行編碼。
ZigZag編碼方式把有符號的整數映射成無符號的整數,因此,絕對值小的整數編碼以後占的位元組數就少,具體的編碼規則:
對於sint32:(n << 1) ^ (n >> 31)
對於sint64:(n << 1) ^ (n >> 63)
注意:n右移31或63位以後就變為全0(正數)或全1(負數)
非變長的數字
double和fixed64的傳輸類型是1,使用固定的64位,float和fixed32的傳輸類型是5,使用固定的32位。都是使用低位元組序(little-endian)。
字串
傳輸類型是2代表:value使用varint編碼的,傳輸類型後面緊跟的就是value所佔的位元組數。比如:
message Test2 {
required string b = 2;
}
如果b的值是testing,編碼以後就是:12 07 74 65 73 74 69 6e 67
key是12(16進位的數值): 0001 0010,後三位010,是2,代表傳輸類型是2,前5位是2,代表序號是2.
長度是07,代表value的長度是7
74(16進位的數值):代表的就是116,字母t。
message嵌套
現在有一個message,嵌套了message Test1:
message Test3 {
required Test1 c = 3;
}
編碼以後是: 1a 03 08 96 01
1a:0001 1010,後三位是010,傳輸類型是2.前5位是3,代表序號是3。
03:代表value的長度是3
08 96 01:跟Test1的表示方式是一樣的。
可選和重複欄位
如果message的欄位是repeated,但是沒有使用[pached=true]選項,編碼以後的message就會有0個或者多個有相同tag的key/value對,這些相同tag的key/value對不需要連續出現,可以跟其他的欄位交錯,但是需要保持這些元素本身的前後順序。
如果message的欄位是repeated,編碼以後可能出現也可能不出現。
一般情況下,編碼以後的message的位二進位流裡面,一個optional和required欄位不應該出現多次,如果出現了,對於數字和字串類型,只接受最後一個,對於嵌套message的欄位,會合并多個執行個體的相同欄位,就如同使用 Message::MergeFrom方法一樣。具體來說:後面出現的標量field會替換前面出現的,嵌入的message繼續合并,repeated欄位會進行拼接。這麼做的結果就是:拼接編碼以後的message等價於先把message拼接然後再編碼。
緊湊的重複欄位
使用[packed=true]選項的repeated欄位。0個元素的field不出現在編碼以後的message裡面,多個元素的field,會把所有的元素編碼進單個的傳輸類型是2的key/value對裡面。
每一個元素都按照普通的方式進行編碼,只是沒有前置的tag。比如說:
message Test4 {
repeated int32 d = 4;
}
假如現在有一個Test4的對象,d的值是3, 270,86942。編碼以後是:
22 // tag (field number 4, wire type 2)
06 // payload size (6 bytes)
03 // first element (varint 3)
8E 02 // second element (varint 270)
9E A7 05 // third element (varint 86942):
22:10 0010,後三位是010,傳輸類型是2,前5位是100,tag是4。
06:代表長度
03:3
8E 02:1000 1110 0000 0010,實際的值是:1 0000 1110,就是256 + 8 + 4 + 2 = 270
9E A7 05:1001 1110 1010 0111 0000 0101,實際值是:10101001110011110,就是:86942
[注意]只有基本的數字類型(varint, 32-bit, 或者64-bit) 才可以聲明成packed。