protobuf能夠跨平台提供輕量的序列化和反序列化,得益於其平台無關的編碼格式,本文就介紹下其中的編碼格式。
Varints
在protobuf中大量使用到了Varints的編碼格式,這是一個可變長度的編碼格式用於編碼整形數字。
Varint的最小單位是byte,即8位,每byte第一位(msb)是標志位用於標記是否還有后續byte。
===1===
0000 0001
===300===
1010 1100 0000 0010
上面300的例子首先讀入第一個字節發現第一位為1,表示還有后續byte,然后讀取后一個byte,第一位為0就判斷已經讀完,然后組裝數值將其余位數取出0101100 0000010,然后反轉並拼接成為000 0010-010 1100,這樣就組成了300。
其他類型
負數,sint與int
在protobuf的定義中sint和int似乎看上去是重復的,但其實這兩種類型的底層編碼式不同的。這里以-3為例:
-3使用int編碼會變成FD FF FF FF FF FF FF FF FF 01,首先這也是varint編碼去掉每個byte的首位標志位然后反轉順序就成了FF FF FF FF FF FF FF FD是-3的補碼。
而使用sint編碼會變成05,貌似和-3沒有關系,其實它是-3經過一個zigzag轉換而得來的。
Signed Original | Encoded As |
---|---|
0 | 0 |
-1 | 1 |
1 | 2 |
-2 | 3 |
2147483647 | 4294967294 |
-2147483648 | 4294967295 |
message編碼
有了上面這些在准備,我們可以進入真正的消息的編碼了。protobuf中每個字段都是根據定義的tag來進行定位的,在序列化的數據中,每個字段首先是一個Varint用於標記tag,其中最后三位使用來標志該字段的數據類型。
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 |
前面的位數用於標志tag,例如0000 1000,去掉第一個標志位后三個類型位,就表示tag為1的字段。
對於repeat類型由於歷史原因,proto2中默認是將數組元素並排排列在內的例如[1,2,3]會保存成[08 01 08 02 08 03]。后來對repeat類型進行了改進引入packed在定義的后面加上packed:
repeated int32 int = 1 [packed=true];
這樣序列化的數據就成了0A 03 01 02 03,其中0A表示使用不定長編碼,之后03表示長度,接下來是數據,就這個例子就能節約1byte。packed只適用於varint或固定長度數值表示的字段,對於string或者嵌套類型不適用,在proto3中支持的類型默認會使用packed。
實例
syntax = "proto2";
message Person {
required string name=1;
required int32 age=2;
repeated Address add=3;
}
message Address{
required string add=1;
}
---實例---
name: "MyName"
age: 18
add {
add: "MyAdd1"
}
add {
add: "MyAdd2"
}
---Hex---
0A 06 4D 79 4E 61 6D 65 10 12 1A 08 0A 06 4D 79 41 64 64 31 1A 08 0A 06 4D 79 41 64 64 32
---解釋---
0A //變長類型tag為1的字符串
06 //name字段6 byte長度
4D 79 4E 61 6D 65 //MyName
10 //varint編碼tag為2
12 //18
1A //變長類型tag為3
08 //Adress 8 byte長度
0A //變長類型tag為1
06 //add字段6 byte長度
4D 79 41 64 64 31 //MyAdd1
1A //變長類型tag為3
08 //Adress 8 byte長度
0A //變長類型tag為1
06 //add字段6 byte長度
4D 79 41 64 64 32 //MyAdd2