1. 程式人生 > 實用技巧 >google protobuf 資料型別_理解Protobuf資料格式解析

google protobuf 資料型別_理解Protobuf資料格式解析

什麼是protobuf?

Protobuf是Google開源的一款類似於json,XML資料交換格式,其內部資料是純二進位制格式,不依賴於語言和平臺,具有簡單,資料量小,快速等優點。目前用於序列化與反序列化官方支援的語言有C++,C#, GO, JAVA, PYTHON。適用於大小在1M以內的資料,因為像在移動裝置平臺,記憶體是很珍貴。

protobuf格式的特點

1. 效率高/效能好

對比XML這種檔案傳輸格式,解析時間大大低於XML解析時間,同時空間上的開銷也減少了很多。

2. 可以很方便的生成檔案

例如我們在進行檔案傳輸時並不需要自己寫很多東西,僅僅定義一個proto檔案即可生成所需的程式碼。

3. 支援“向後相容”和“向前相容”

“向後相容”(backward compatible),就是說,當模組B升級了後,它能夠正確識別模組A發出的老版本的協議。由於老版本沒有“狀態”這個屬性,在擴充協議時,可以考慮把“狀態”屬性設定成非必填 的,或者給“狀態”屬性設定一個預設值即可。

“向前相容”(forward compatible),就是說,當模組A升級了之後, 模組B能夠正常識別模組A發出的新版本的協議。這時候,新增加的“狀態”屬性會被忽略。因此其可擴充套件性也很強。

4. 支援多種語言

Google官方釋出的原始碼中包含了C++、Java、Python三種語言,這種天生就支援三種語言的格式簡直少見,下面小編會使用python來為大家進行簡單的講解。

資源網站大全 https://55wd.com 我的007辦公資源網站 https://www.wode007.com

Protobuf的使用

使用方法也比較簡單:

  • 定義用於訊息檔案.proto
  • 使用protobuf的編譯器編譯訊息檔案
  • 使用編譯好對應語言的類檔案進行訊息的序列化與反序列化

先來定義一個簡單的訊息:

message Person {
   int32 id = 1;//24
   string name = 2;//wujingchao
   string email = 3;//[email protected]
}

實際的二進位制訊息為:

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

下面就講解這段二進位制流資料是怎麼組成的:

varints

一般情況下int型別都是固定4個位元組,protobuf定義了一種變長的int,每個位元組最高位表示後面還有沒有位元組,低7位就為實際的值,並且使用小端的表示方法。例如1,varint的表示方法就為:

0000 0001

是不是這樣就省了三個位元組。

再例如300,4位元組表示為:10 0101100,varint表示為:

10101100 00000010

所以前面訊息為Person的id的值為00011000,即0x18。

負數的最高位為1,如果負數也使用這種方式表示就會出現一個問題,int32總是需要5個位元組,int64總是需要10個位元組。

所以定義了另外一種型別:sint32,sint64。採用ZigZag編碼,所有的負數都使用正數表示,計算方式:

sint32
(n << 1) ^ (n >> 31)
sint64
(n << 1) ^ (n >> 63)
Signed OriginalEncoded As
0 0
-1 1
1 2
-2 3
2147483647 4294967294
-2147483648 4294967295

使用varint編碼的型別有int32, int64, uint32, uint64, sint32, sint64, bool, enum。Java裡面沒有對應的無符號型別,int32與uint32一樣。

Wire Type

每個訊息項前面都會有對應的tag,才能解析對應的資料型別,表示tag的資料型別也是varint。

tag的計算方式: (field_number << 3) | wire_type

每種資料型別都有對應的wire_type:

Wire TypeMeaning 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,所以對應的tag為

1 <<< 3 | 0  = 0x08

Person的name,field_number為2,wire_type為2,所以對應的tag為

2 <<< 3 | 2 = 0x12

對應Length-delimited的wire type,後面緊跟著的varint型別表示資料的位元組數。

所以name的tag後面緊跟的0x0a表示後面的資料長度為10個位元組,即"wujingchao"的UTF-8 編碼或者ASCII值:

08 18 12 0a77 75 6a 69 6e 67 63 68 61 6f1a 16

巢狀的訊息型別embedded messages與packed repeated fields也是使用這種方式表示,對應預設值的資料,是不會寫進protobuf訊息裡面的。

packed repeated與repeated的區別在於編碼方式不一樣,repeated將多個屬性型別與值分開儲存。而packed repeated採用Length-delimited方式。下面這個是官方文件的例子:

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)

  

如果沒有packed的屬性是這樣儲存的:

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)

  

是不是這種方式比較節省記憶體,所以proto3的repeated預設就是使用packed這種方式來儲存。(proto2與proto3區別在於.proto的語法)。