protobuf编码
protobuf能夠跨平臺提供輕量的序列化和反序列化,得益于其平臺無關的編碼格式,本文就介紹下其中的編碼格式。
Varints
在protobuf中大量使用到了Varints的編碼格式,這是一個可變長度的編碼格式用于編碼整形數字。
Varint的最小單位是byte,即8位,每byte第一位(msb)是標志位用于標記是否還有后續byte。
上面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轉換而得來的。
| 0 | 0 |
| -1 | 1 |
| 1 | 2 |
| -2 | 3 |
| 2147483647 | 4294967294 |
| -2147483648 | 4294967295 |
這樣轉換可以大大減少負數的表示長度,所以建議在經常出現負數的地方使用sint而不是int可以縮短編碼的大小。
非varient數值
對于double/fixed64,float/fixed32會分別使用固定的64位或32位進行表示。
不定長數據類型
對于string,byte等類型,就使用這種類型,首先使用一個varint表示長度,之后是數據的內容,列入字符串aaa就是03 61 61 61 其中03表示長度為3,61是a的utf8編碼。值得注意的是,嵌套的message類型、repeat字段也都是使用這種形式進行編碼到對象中的。
message編碼
有了上面這些在準備,我們可以進入真正的消息的編碼了。protobuf中每個字段都是根據定義的tag來進行定位的,在序列化的數據中,每個字段首先是一個Varint用于標記tag,其中最后三位使用來標志該字段的數據類型。
| 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:
這樣序列化的數據就成了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轉載于:https://www.cnblogs.com/resentment/p/6617787.html
總結
以上是生活随笔為你收集整理的protobuf编码的全部內容,希望文章能夠幫你解決所遇到的問題。