Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據序列化,適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平台無關、可擴展的序列化結構數據格式。
字段規則
required: 字段必須存在optional: 字段沒有或有一個repeated: 字段重復,0個或多個
proto 數據類型
|
.proto Type
|
Notes
|
C++ Type
|
Java Type
|
Python Type
[2]
|
Go Type
|
|---|---|---|---|---|---|
| double | 固定8字節長度 | double | double | float | *float64 |
| float | 固定4字節長度 | float | float | float | *float32 |
| int32 | 可變長度編碼。對負數編碼低效,如果字段可能是負數,用sint32代替 | int32 | int | int | *int32 |
| int64 | 可變長度編碼。對負數編碼低效,如果字段可能是負數,用sint64代替 | int64 | long | int/long[3] | *int64 |
| uint32 | 可變長度編碼,無符號整數 | uint32 | int[1] | int/long[3] | *uint32 |
| uint64 | 可變長度編碼,無符號整數 | uint64 | long[1] | int/long[3] | *uint64 |
| sint32 | 可變長度編碼。有符號整數。 These more efficiently encode negative numbers than regular int32s. | int32 | int | int | *int32 |
| sint64 | 可變長度編碼。有符號整數。These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long[3] | *int64 |
| fixed32 | 固定4字節長度,無符號整數。 More efficient than uint32 if values are often greater than 228. | uint32 | int[1] | int/long[3] | *uint32 |
| fixed64 | 固定8字節長度,無符號整數。 More efficient than uint64 if values are often greater than 256. | uint64 | long[1] | int/long[3] | *uint64 |
| sfixed32 | 固定4字節長度,有符號整數 | int32 | int | int | *int32 |
| sfixed64 | 固定8字節長度,有符號整數 | int64 | long | int/long[3] | *int64 |
| bool | bool | boolean | bool | *bool | |
| string | UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode[4] | *string |
| bytes | 包含任意字節序列 | string | ByteString | str | []byte |
編碼規則
1.varints
理解簡單protobuf編碼,首先要知道varints。varints使用一個字節或多個字節對整數序列化方法。
varints中的每個字節除了最后一個字節,有一個最有效位(most significant bit ,msb),這意味指示之后有其他字節。每個字節的低7位一組數的補碼
例如:
1
0000 0001
300
1010 1100 0000 0010
只取每個字節低七位
010 1100 000 0010
小端序->大端序
000 0010 010 1100 --> 0001 0010 1100 =300
2.消息結構
protobuf 消息是一系列key-value對,對於二進制消息,字段數字作為關鍵字。字段的命名和類型在解碼時確定。
在進行消息編碼時,key/value被連接成字節流。在解碼時,解析器可以直接跳過不識別的字段,這樣就可以保證新老版本消息定義在新老程序之間的兼容性,從而有效的避免了使用older消息格式的older程序在解析newer程序發來的newer消息時,一旦遇到未知(新添加的)字段時而引發的解析和對象初始化的錯誤。最后,我們介紹一下字段標號和字段類型是如何進行編碼的。每一個 wire-format消息的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 = (field_number << 3) | wire_type key的最后3個bits用於存儲字段的類型信息。那么在使用該編碼時,Protocol Buffer所支持的字段類型將不會超過8種。
例如:150(十進制) 在protobuf二進制文件中是 08 96 01
08 --> 00001000 field_number=1,wire_type=0
96 01 -> 1001 0110 0000 0001 --> 001 0110 000 0001 --> 000 0001 001 0110 --> 1001 0110 =150
3.Signed Integers
wire_type=0 的類型都以varint 進行編碼,所以對於int32和int64,對於負數使用補碼,int32 需要5個字節,int64需要10個字節,有符號整數使用ZIgZag編碼
ZigZag 將有符號整數映射到無符號整數,以此得到一個絕對值較小的數字(例如-1)j就可以獲得一個較小的varint編碼值。
|
Signed Original
|
Encoded As
|
|---|---|
| 0 | 0 |
| -1 | 1 |
| 1 | 2 |
| -2 | 3 |
| 2147483647 | 4294967294 |
| -2147483648 | 4294967295 |
sint32: (n << 1) ^ (n >> 31)
sint64: (n << 1) ^ (n >> 63)
4.Non-varint Numbers
wire-type=1:fixed64, sfixed64, double 固定64bit
wire-type=5:fixed32, sfixed32, float,固定32bit
5.Strings
wire-type=2,字符串長度使用varint編碼
例如
message Test2 {
optional string b = 2;
}
b=“testing” ,編碼后結果:
12 07 74 65 73 74 69 6e 67
12: field number=2,wire-type=2 (2<< 3)|2=0x12
長度為7 varint編碼 0x07
74 65 73 74 69 6e 67 UTF8 編碼
6.Embedded Messages嵌套消息
wire-type=2
message Test1 {
optional int32 a = 1;
}
message Test3 {
optional Test1 c = 3;
}
Test1's a field set to 150
Test3 編碼結果: 1a 03 08 96 01
1a: field_number=3,wire_type=2 (3<< 3)|2=11010=0x1a
03: 字節數
08 96 01 : c編碼結果
7.Optional And Repeated Elements
proto2 :消息被定義repeated (沒有 [packed=true]選項),編碼的消息有0或多個使用相同字段標號的key-value對,這些重復的值不必連續出現; 他們可能會與其他字段交錯,但在解碼時順序保留
optional字段,編碼后的消息可能有也可能沒有包含該字段號的鍵值對。
proto3 使用packed encoding:
包含零個元素的打包重復字段不會出現在編碼消息中。這個字段的所有元素都打包到一個wire-type=2的key-value對中
message Test4 {
repeated int32 d = 4 [packed=true];
}
22 // key (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)
proto2默認不設置 packed=true
repeated編碼采用空格(0x20)分隔
結果是20 03 20 8e 02 20 9e a7 05
