protocalbuffer序列化原理

以前項目中使用過protocolbuffer進行序列化,當時就只是使用了protocalbuffer的工具生成了一個類的序列化工具。今天研究公司的序列化項目,發現應該是借用protocalbuffer的序列化思想來實現的,特地研究一下protocalbuffer的序列化實現原理。html

主要思想

將對象的字段的類型字段名序列化爲tag表示,值序列化爲value。框架

存儲時按照t-l-v的順序,先序列化tag類型標籤,在記錄length長度,在記錄value值。 固定長度是不須要length。工具

protocalbuffer序列化方式

tag

記錄字段的標識號(fieldnumber)和 數據類型(wiretype),即Tag = 字段數據類型(wiretype) + 標識號(fieldnumber) 佔用 一個字節 的長度(若是標識號超過了16,則佔用多一個字節的位置)性能

具體實現編碼

// Tag 的具體表達式以下

Tag = (fieldnumber << 3) | wiretype 

// 參數說明:

// fieldnumber:對應於 .proto文件中消息字段的標識號,表示這是消息裏的第幾個字段 

// fieldnumber << 3:表示 fieldnumber = 將 Tag的二進制表示 右移三位 後的值 

// fieldnum左移3位不會致使數據丟失,由於表示範圍仍是足夠大地去表示消息裏的字段數目

// wiretype:表示 字段 的數據類型

// wiretype = Tag的二進制表示 的最低三位值 

// wiretype的取值 enum WireType { WIRETYPEVARINT = 0, WIRETYPEFIXED64 = 1, WIRETYPELENGTHDELIMITED = 2, WIRETYPESTARTGROUP = 3, WIRETYPEENDGROUP = 4, WIRETYPEFIXED32 = 5 };

// 從上面能夠看出,wire_type最多佔用 3位 的內存空間(由於 3位 足以表示 0-5 的二進制)

先介紹一下最主要的兩種編碼方式,在對字段名和類型的標識中會用到這兩種編碼方式。spa

writetype=0時的序列化

主要用到了varint類型的編碼和zigzag編碼code

varint編碼

一種變長的編碼方式htm

varint編碼

主要思想就是:每一個字節的第一位標識後面本字節是不是當前value的最後一個字節,剩下七位表示value的值,因此若是值很小的時候能夠用一個字節就能表示。對象

不足之處

由於一般序列化的value值都比較小,因此一般用vartint編碼均可以減小字節長度,可是若是值比較大就會佔用到5個字節。負數由於首位的符號位是1,因此若是用varint標識負數都會使用5個字節。

zigzag編碼

一種變長的編碼方式blog

zigzag編碼

主要思想就是:先使用ZigZig編碼將負數映射成正數,而後再使用Varint編碼。

實際數字的表示以下圖所示:

zigzag實例

Wire Type = 1& 5時的序列化

編碼後的數據具有固定大小 = 64位(8字節) / 32位(4字節),都是高位在後,低位在前,採用t-v方式存儲。

Wire Type = 2時的序列化

對length使用varint方式編碼,對於value值爲string類型的使用utf-8方式編碼。對於value值爲message類型的再根據message的類型進行選擇。

message爲嵌套類型時,根據嵌套的類型應該用上述的類型序列化均可以解決。

message爲repeat類型時不帶packed=true,會致使Tag的冗餘,即相同的Tag存儲屢次。

使用建議

根據上面的序列化原理分析,我總結出如下Protocol Buffer的使用建議

  • 建議1:多用 optionalrepeated修飾符
    由於若optionalrepeated 字段沒有被設置字段值,那麼該字段在序列化時的數據中是徹底不存在的,即不須要進行編碼

    相應的字段在解碼時纔會被設置爲默認值

  • 建議2:字段標識號(Field_Number)儘可能只使用 1-15,且不要跳動使用
    由於Tag裏的Field_Number是須要佔字節空間的。若是Field_Number>16時,Field_Number的編碼就會佔用2個字節,那麼Tag在編碼時也就會佔用更多的字節;若是將字段標識號定義爲連續遞增的數值,將得到更好的編碼和解碼性能

  • 建議3:若須要使用的字段值出現負數,請使用 sint32 / sint64,不要使用int32 / int64
    由於採用sint32 / sint64數據類型表示負數時,會先採用Zigzag編碼再採用Varint編碼,從而更加有效壓縮數據

  • 建議4:對於repeated字段,儘可能增長packed=true修飾
    由於加了packed=true修飾repeated字段採用連續數據存儲方式,即T - L - V - V -V方式

序列化 & 反序列化過程

序列化過程以下:
1. 判斷每一個字段是否有設置值,有值才進行編碼
2. 根據 字段標識號&數據類型 將 字段值 經過不一樣的編碼方式進行編碼

反序列化過程以下:
1. 調用 消息類的 parseFrom(input) 解析從輸入流讀入的二進制字節數據流
2. 將解析出來的數據 按照指定的格式讀取到 JavaC++Phyton 對應的結構類型中

因爲:
a. 編解碼方式簡單(只須要簡單的數學運算 = 位移等等)
b. 採用 Protocol Buffer 自身的框架代碼 和 編譯器 共同完成

因此Protocol Buffer的序列化和反序列化速度很是快。

發現一篇文章https://halfrost.com/protobuf_encode/

負數在計算機中的表示-128https://www.cnblogs.com/daxin/p/4093644.html

相關文章
相關標籤/搜索