Protobuf數(shù)據(jù)格式解析

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昌执,一起剝皮案震驚了整個(gè)濱河市烛亦,隨后出現(xiàn)的幾起案子诈泼,更是在濱河造成了極大的恐慌,老刑警劉巖此洲,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厂汗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡呜师,警方通過(guò)查閱死者的電腦和手機(jī)娶桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汁汗,“玉大人衷畦,你說(shuō)我怎么就攤上這事≈疲” “怎么了祈争?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)角寸。 經(jīng)常有香客問(wèn)我菩混,道長(zhǎng),這世上最難降的妖魔是什么扁藕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任沮峡,我火速辦了婚禮,結(jié)果婚禮上亿柑,老公的妹妹穿的比我還像新娘邢疙。我一直安慰自己,他們只是感情好望薄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布疟游。 她就那樣靜靜地躺著,像睡著了一般痕支。 火紅的嫁衣襯著肌膚如雪颁虐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天卧须,我揣著相機(jī)與錄音聪廉,去河邊找鬼。 笑死故慈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的框全。 我是一名探鬼主播察绷,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼津辩!你這毒婦竟也來(lái)了拆撼?” 一聲冷哼從身側(cè)響起容劳,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闸度,沒(méi)想到半個(gè)月后竭贩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莺禁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年留量,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哟冬。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡楼熄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浩峡,到底是詐尸還是另有隱情可岂,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布翰灾,位于F島的核電站缕粹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纸淮。R本人自食惡果不足惜平斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萎馅。 院中可真熱鬧双戳,春花似錦、人聲如沸糜芳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)峭竣。三九已至塘辅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間皆撩,已是汗流浹背扣墩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扛吞,地道東北人呻惕。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像滥比,于是被迫代替她去往敵國(guó)和親亚脆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容