Protobuf底層存儲原理

參考官網, 序列化原理算法

底層二進制存儲

message Test1 {
optional int32 a = 1;
}ui

並設置爲a=150,序列化到一個文件中,查看文件,獲得下面的二進制:
08 96 01
從底層存儲的二進制值看出,Protobuf爲何這麼快,節省內存了吧。google

有以上的結果是由於 varints 這個特殊的東東。它可讓已個int數據類型的存儲根據值的大小而自動改變存儲的字節數。編碼

varint 中的每一個字節,除了最後一個字節,都有最重要的位集——這表示還會有更多的字節。每一個字節的低7位用於存儲以7位爲一組的數字的兩個補碼錶示形式,最早存儲的是最低字節。code

好比存儲數字1,請看二進制格式:
0000 0001
由於只有一個字節,因此最高位是0.內存

好比存儲數字300,請看二進制格式:
1010 1100 0000 0010
計算方法:字符串

  • 1.先刪除最高位。由於這位時沒意義的,只是告訴咱們是否叨叨數字的末尾。
    1010 1100 0000 0010
    → 010 1100 000 0010
  • 2.翻轉字節。由於varint最早存儲的是最低字節。
    010 1100 000 0010
    →000 0010 010 1100
  • 3.字節相加。還原最終的值。
    → 000 0010 ++ 010 1100
    → 100101100
    → 256 + 32 + 8 + 4 = 300

Protobuf 的快,小就是經過以上來實現的了。。。。。。get

消息結構(Message Structure)

Protobuf 是一系列鍵值對。消息的二進制版本只使用字段的標籤做爲,每一個字段的名稱和聲明類型只能在解碼結束時經過引用消息類型的定義來肯定。原型

當對消息進行編碼時,鍵和值被鏈接到一個字節流中。當消息被解碼時,解析器可以跳過它不認識的字段。經過這種方式,可使舊代碼(相對Protobuf消息定義的新舊)可以兼容新的字段而不用修改代碼。爲此,行格式消息中每對的「鍵」其實是兩個值——.proto文件中的字段號+一個線類型,經過該類型能夠推斷出數據長度。在大多數語言實現中,這個鍵被稱爲標記。string

數據類型:
| 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|

流消息中的每一個鍵都是一個varint,其值爲(field_number << 3) | wire_type,也就是說,數字的最後三位存儲了存儲數據包的類型。

例如:
底層存儲二進制是:
000 1000

那麼原字段的類型就是根據(field_number << 3) | wire_type獲得低三位獲得 wire( 0 ),是一個 Varint 類型,也就是數字。剩下的幾位右移,獲得的是1,所以字段標籤是1

因此字段原型應該是:
struct Message {
int32 | int64 | uint32 | uint64 | sint32 | sint64 | bool | enum xxx = 1;
};

再來看下兩個字節的150:

  • 1.十六進制值爲:
    96 01
  • 2.轉換爲二進制格式:
    1001 0110 0000 0001
  • 3.丟棄最後一位(沒意義,只是判斷是不是最後一個字節),而且翻轉字節(varint是先存儲最低字節),最後穿起來獲得真正的二進制值,進而獲得原值。
    • → 000 0001 ++ 001 0110 (drop the msb and reverse the groups of 7 bits)
      → 10010110
      → 128 + 16 + 4 + 2 = 150

由此看見,多字節的二進制存儲,就是多了丟棄最後一位翻轉字節的步驟。

更多數據類型

有符號整型(Signed Integers)

在Protobuf中,有符號的編碼是利用了ZigZag編碼,把有符號類型編碼成一個比較大的無符號整型,提升了存儲空間和提升序列化速度。

ZigZag編碼是一種應用於大量使用小整型的場景的編碼算法,能夠提升編碼速度。

非varint數字(Non-varint Numbers)

類型1,類型5的數字是按照小端序列來存儲的。

Strings

類型爲2(以長度分隔)意味着該值是varint編碼的長度,後跟指定的數據字節數。
看這個例子:

message Test2 {
  optional string b = 2;
}

在應用程序裏設置 b 值爲testing,序列化後獲得下面的二進制串:
12 07 74 65 73 74 69 6e 67

加粗的字節是「testing」的UTF8。這裏的鍵是0x12→字段號= 2,類型= 2。值中的varint長度是7,你看,咱們在它後面找到了7個字節——咱們的字符串。

嵌入類型(Embedded Messages)

message Test1 {
  optional int32 a = 1;
}
message Test3 {
  optional Test1 c = 3;
}

設置Test1的 a 爲150,獲得序列化十六進制值:
1a 03 08 96 01

最後三個字節與上面的第一個示例單獨Test1並賦值150 (08 96 01)徹底相同,它們的前面是數字3,嵌入式消息的處理方式與字符串徹底相同(wire type = 2)。

相關文章
相關標籤/搜索