protobuf中的編碼規則函數
(1)序列化和反序列化:ui
在開始本部分的內容以前,首先有必要介紹兩個基本概念,一個是序列化,一個是反序列化。這兩個概念的定義在網上搜一下都不少的,但大多都講得比較晦澀,不太好理解,在這裏我會用比較通俗的文字來解釋,儘量讓讀都朋友們一讀就明白是怎麼回事:編碼
序列化:是指將結構化的數據按必定的編碼規範轉成指定格式的過程;spa
反序列化:是指將轉成指定格式的數據解析成原始的結構化數據的過程。code
舉個例子,Person是一個表示人的對象類型,person是一個Person類型的對象,將person存到一個對應的XML文檔中的過程就是一種序列化,而解析XML生成對應Person類型對象person的過程,就是一個反序列化的過程。在這裏結構化數據指的就是Person類型的數據,必定的編碼規範指的就是XML文檔的規範。XML是一種簡單的序列化方式,用XML序列化的好處是,XML的通用性比較好,另外,XML是一種文本格式,對人閱讀比較友好,可是XML方式比較佔空間,效率也不是很高。一般,比較高效的序列化都是採用二進制方式的,將要序列化的結構化數據,按必定的編碼規範,轉成爲一串二進制的字節流存儲下來,須要用的時候再從這串二進制的字節流中反序列化出對應的結構化的數據。orm
經過上面的介紹,咱們給protobuf下一個比較正式的定義了:Google ProtoBuf是Google制定的一種用來序列化結構化數據的程序庫。對象
(2)ProtoBuf中的編碼:文檔
1) ProtoBuf編碼基礎——Varints, varints是一種將一個整數序列化爲一個或者多個Bytes的方法,越小的整數,使用的Bytes越少。string
Varints的基本規則是:it
(a)每一個Byte的最高位(msb)是標誌位,若是該位爲1,表示該Byte後面還有其它Byte,若是該位爲0,表示該Byte是最後一個Byte。
(b)每一個Byte的低7位是用來存數值的位。
(c)Varints方法用Litte-Endian(小端)字節序
舉個例子:300用Varints序列化的結果是1010 1100 0000 0010,運算過程以下 所示:
1010 1100 0000 0010->010 1100 000 0010(去標誌位)->
000 0010 010 1100(調整字節序)-> 1 0010 1100 ->256+32+8+4=300(計算值)
2)ProtoBuf中消息的編碼規則:
(a)每條消息(message)都是有一系列的key-value對組成的, key和value分別採用不一樣的編碼方式。
(b)對某一條件消息(message)進行編碼的時候,是把該消息中全部的key-value對序列化成二進制字節流;而解碼的時候,解碼程序讀入二進制的字節流,解析出每個key-value對,若是解碼過程當中遇到識別不出來的類型,直接跳過。這樣的機制,保證了即便該消息添加了新的字段,也不會影響舊的編/解碼程序正常工做。
(c)key由兩部分組成,一部分是在定義消息時對字段的編號(field_num),另外一部分是字段類型(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 |
(d)key的編碼方式:field_num << 3 | wire_type
(e)varint類型(wire_type=0)的編碼,與第(1)部分中介紹的方法基本一致,可是int32, int64和sint32,sint64有些特別之處:int32和int64就是簡單的按varints方法來編碼,因此像-1、-2這樣負數也會佔比較多的Bytes。因而sint32和sint64採用了一種改進的方法:先採用Zigzag方法將全部的整數(正數、0和負數)一一映射到全部的無符號數上,而後再採用varints編碼方法進行編碼。Zigzag映射函數爲:
Zigzag(n) = (n << 1) ^ (n >> 31), n爲sint32時
Zigzag(n) = (n << 1) ^ (n >> 63), n爲sint64時
下表是一個比較直觀的映射表,這樣映射後再進行編碼的好處就是絕對值比較小的負數序列化後的結果佔的Bytes數也會比較少。
Signed Original |
Encoded As |
0 |
0 |
-1 |
1 |
1 |
2 |
-2 |
3 |
2 |
4 |
-3 |
5 |
… |
… |
2147483647 |
4294967294 |
-2147483648 |
4294967295 |
(f)64-bit(wire_type=1)和32-bit(wire_type=5)的編碼方式就比較簡單了,直接在key後面跟上64bits或32bits,採用Little-Endian(小端)字節序。
(g)length-delimited(wire_type=2)的編碼方式:key+length+content, key的編碼方式是統一的,length採用varints編碼方式,content就是由length指定的長度的Bytes。
(h)wire_type=3和4的如今已經不推薦使用了,所以這裏也再也不作介紹。
3)ProtoBuf編解碼中字段順序(Field order)的問題:
(a) 編碼/解碼與字段順序無關,這一點由key-value機制就能保證
(b)對於未知的字段,編碼的時候會把它寫在序列化完的已知字段後面。
原文
http://www.wuzesheng.com/?p=1258