Protobuf是Google開(kāi)源的一款類(lèi)似于Json嘶炭,XML數(shù)據(jù)交換格式裂逐,其內(nèi)部數(shù)據(jù)是純二進(jìn)制格式歹鱼,不依賴(lài)于語(yǔ)言和平臺(tái),具有簡(jiǎn)單卜高,數(shù)據(jù)量小弥姻,快速等優(yōu)點(diǎn)。目前用于序列化與反序列化官方支持的語(yǔ)言有C++掺涛,C#庭敦, GO, JAVA薪缆, PYTHON秧廉。適用于大小在1M以?xún)?nèi)的數(shù)據(jù),因?yàn)橄裨谝苿?dòng)設(shè)備平臺(tái),內(nèi)存是很珍貴疼电。
使用方法也比較簡(jiǎn)單:
- 定義用于消息文件.proto
- 使用protobuf的編譯器編譯消息文件
- 使用編譯好對(duì)應(yīng)語(yǔ)言的類(lèi)文件進(jìn)行消息的序列化與反序列化
先來(lái)定義一個(gè)簡(jiǎn)單的消息:
message Person {
int32 id = 1;//24
string name = 2;//wujingchao
string email = 3;//wujingchao92@gmail.com
}
實(shí)際的二進(jìn)制消息為:
08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16 77 75 6a 69 6e 67 63 68 61 6f 39 32 40 67 6d 61 69 6c 2e 63 6f 6d
下面就講解這段二進(jìn)制流數(shù)據(jù)是怎么組成的:
Varints
一般情況下int類(lèi)型都是固定4個(gè)字節(jié)嚼锄,protobuf定義了一種變長(zhǎng)的int,每個(gè)字節(jié)最高位表示后面還有沒(méi)有字節(jié)蔽豺,低7位就為實(shí)際的值区丑,并且使用小端的表示方法。例如1,varint的表示方法就為:
0000 0001
是不是這樣就省了三個(gè)字節(jié)修陡。
再例如300,4字節(jié)表示為:10 0101100,varint表示為:
10101100 00000010
所以前面消息為Person的id的值為00011000沧侥,即0x18。
負(fù)數(shù)的最高位為1魄鸦,如果負(fù)數(shù)也使用這種方式表示就會(huì)出現(xiàn)一個(gè)問(wèn)題,int32總是需要5個(gè)字節(jié)宴杀,int64總是需要10個(gè)字節(jié)。
所以定義了另外一種類(lèi)型:sint32,sint64号杏。采用ZigZag編碼婴氮,所有的負(fù)數(shù)都使用正數(shù)表示,計(jì)算方式:
- sint32
(n << 1) ^ (n >> 31) - sint64
(n << 1) ^ (n >> 63)
Signed Original | Encoded As |
---|---|
0 | 0 |
-1 | 1 |
1 | 2 |
-2 | 3 |
2147483647 | 4294967294 |
-2147483648 | 4294967295 |
使用Varint編碼的類(lèi)型有int32, int64, uint32, uint64, sint32, sint64, bool, enum。Java里面沒(méi)有對(duì)應(yīng)的無(wú)符號(hào)類(lèi)型盾致,int32與uint32一樣主经。
Wire Type
每個(gè)消息項(xiàng)前面都會(huì)有對(duì)應(yīng)的tag,才能解析對(duì)應(yīng)的數(shù)據(jù)類(lèi)型庭惜,表示tag的數(shù)據(jù)類(lèi)型也是Varint罩驻。
tag的計(jì)算方式: (field_number << 3) | wire_type
每種數(shù)據(jù)類(lèi)型都有對(duì)應(yīng)的wire_type:
Wire 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 |
所以wire_type最多只能支持8種,目前有6種护赊。
所以前面Person的id,field_number為1,wire_type為0惠遏,所以對(duì)應(yīng)的tag為
1 <<< 3 | 0 = 0x08
Person的name,field_number為2,wire_type為2,所以對(duì)應(yīng)的tag為
2 <<< 3 | 2 = 0x12
對(duì)應(yīng)Length-delimited的wire type,后面緊跟著的Varint類(lèi)型表示數(shù)據(jù)的字節(jié)數(shù)。
所以name的tag后面緊跟的0x0a表示后面的數(shù)據(jù)長(zhǎng)度為10個(gè)字節(jié)骏啰,即"wujingchao"的UTF-8 編碼或者ASCII值:
08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16
嵌套的消息類(lèi)型embedded messages與packed repeated fields也是使用這種方式表示节吮,對(duì)應(yīng)默認(rèn)值的數(shù)據(jù),是不會(huì)寫(xiě)進(jìn)protobuf消息里面的判耕。
packed repeated與repeated的區(qū)別在于編碼方式不一樣透绩,repeated將多個(gè)屬性類(lèi)型與值分開(kāi)存儲(chǔ)。而packed repeated采用Length-delimited方式壁熄。下面這個(gè)是官方文檔的例子:
message Test4 {
repeated int32 d = 4 [packed=true];
}
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)
如果沒(méi)有packed的屬性是這樣存儲(chǔ)的:
20 //tag(field number 4,wire type 0)
03 //first element (varint 3)
20 //tag(field number 4,wire type 0)
8E 02//second element (varint 270)
20 //tag(field number 4,wire type 0)
9E A7 05 // third element (varint 86942)
是不是這種方式比較節(jié)省內(nèi)存帚豪,所以proto3的repeated默認(rèn)就是使用packed這種方式來(lái)存儲(chǔ)。(proto2與proto3區(qū)別在于.proto的語(yǔ)法)草丧。
有了以上的相關(guān)概念狸臣,我們?cè)谧xprotobuf的源碼就比較容易了。
參考:https://developers.google.com/protocol-buffers/docs/encoding