以前項目中使用過protocolbuffer進行序列化,當時就只是使用了protocalbuffer的工具生成了一個類的序列化工具。今天研究公司的序列化項目,發現應該是借用protocalbuffer的序列化思想來實現的,特地研究一下protocalbuffer的序列化實現原理。html
將對象的字段的類型和字段名序列化爲tag表示,值序列化爲value。框架
存儲時按照t-l-v的順序,先序列化tag類型標籤,在記錄length長度,在記錄value值。 固定長度是不須要length。工具
記錄字段的標識號(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
主要用到了varint類型的編碼和zigzag編碼code
一種變長的編碼方式htm
主要思想就是:每一個字節的第一位標識後面本字節是不是當前value的最後一個字節,剩下七位表示value的值,因此若是值很小的時候能夠用一個字節就能表示。對象
一種變長的編碼方式blog
主要思想就是:先使用ZigZig編碼將負數映射成正數,而後再使用Varint編碼。
實際數字的表示以下圖所示:
編碼後的數據具有固定大小 = 64位(8字節) / 32位(4字節),都是高位在後,低位在前,採用t-v方式存儲。
對length使用varint方式編碼,對於value值爲string類型的使用utf-8方式編碼。對於value值爲message類型的再根據message的類型進行選擇。
message爲嵌套類型時,根據嵌套的類型應該用上述的類型序列化均可以解決。
message爲repeat類型時不帶packed=true
,會致使Tag的冗餘,即相同的Tag存儲屢次。
根據上面的序列化原理分析,我總結出如下Protocol Buffer
的使用建議
建議1:多用 optional
或 repeated
修飾符
由於若optional
或 repeated
字段沒有被設置字段值,那麼該字段在序列化時的數據中是徹底不存在的,即不須要進行編碼
相應的字段在解碼時纔會被設置爲默認值
建議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. 將解析出來的數據 按照指定的格式讀取到 Java
、C++
、Phyton
對應的結構類型中
因爲:
a. 編解碼方式簡單(只須要簡單的數學運算 = 位移等等)
b. 採用 Protocol Buffer
自身的框架代碼 和 編譯器 共同完成
因此Protocol Buffer
的序列化和反序列化速度很是快。
發現一篇文章https://halfrost.com/protobuf_encode/
負數在計算機中的表示-128https://www.cnblogs.com/daxin/p/4093644.html