protobuf編碼原理

前言

在一個message中,編號要唯一,編號爲1-15的佔用1個字節,16-2047的佔用2個字節,範圍是[1-536,870,911],其中[19000,19999]保留給協議使用。爲什麼不同大小的編號佔用不同的字節數?

下面有一個簡單的message示例,假設a的值爲150 ,下面的消息在序列化之後,僅佔用3個字節,轉換爲10進制後爲08 96 01。如何編碼的?

message Test1 {
  optional int32 a = 1;
}

Varints

Varints是一種使用一個或多個字節來序列化整數的方法,小的數字佔用較少的字節數。一個字節的最高位(從左到右是高到低)用來表示是否已經表示完整,是一個標誌位,稱爲msb,爲1表示後面還有。每個字節的低7位用於以7位爲一組存儲數字的二進制補碼錶示,最低有效組在前(least significant group first)。

比如數字300的Varints編碼後的二進制爲:1010 11000000 0010

  1. 第一個字節最高位爲1,第二個字節最高位爲0,所以這兩個字節纔是一個完整的數字。
  2. 將每個字節的最高位去掉,得到:010 1100 000 0010
  3. 因爲最低有效組在前面,所以反轉第2步的結果,即 000 0010010 1100
  4. 將3步得到的結果連接起來,前面的0刪除掉,得到:100101100,換算爲10進制即300。

wire type

消息編碼後是二進制字節流,所以如何從沒有意義的字節流中解析出實際的數據,這就需要一種規則。鍵和值是連在一起的,中間沒有任何特殊字符的分隔,所以問題是如何找到鍵是什麼,值是什麼?

消息是鍵值對結構,鍵是字段的編號而非名稱,所以字段的名稱和聲明的類型只能在解碼時確定。因爲鍵是一個整數類型,所以它總是用varint編碼來表示。 對於值來講,它還有浮點型,字符串,字節類型等等,所以需要其他的編碼方式來處理這些額外的類型,它們也可能佔用不定長的字節數,但是都必須像Varints編碼一樣,能夠有一定的規則來確定長度。

所以對消息的鍵編碼不僅僅要包含鍵的編號是多少,還要包含它的編碼類型,在解析完鍵之後,得到編碼類型,就能進一步確定值佔用幾個字節。這裏的編碼類型,官方文檔叫做wire type,總共列出了5中 wire types:

由於只有5種情況,所以3bit就夠存儲了。

解釋

回到開始的問題,消息 Test1編碼後是08 96 01,鍵在前,值在後,鍵使用varint編碼,其中應當包括編號和wire type。08的二進制是0000 1000,最高爲0,說明這個字節是完整的,最低位的3bit表示 wire type,即000也就是說明這是個Varints編碼,去掉最高位和最低3位,剩下0001表示編號,即1。

緊跟着的是96,換成二進制爲1001 0110,最高位爲1,又因爲鍵的解碼錶明瞭是varint編碼,說明不完整,繼續找下一個字節爲0000 0001,最高位是0,所以兩個字節表示值,解碼後爲150。

So you should reserve the numbers 1 through 15 for very frequently occurring message elements. Remember to leave some room for frequently occurring elements that might be added in the future.

爲什麼要優先使用1-15編號?去掉最高1位,去掉最低3位,還剩4位,所以4位只能存儲0-15,因爲編號是從1開始,所以1-15佔用1個字節,比15大的編號就佔用更多的字節。16-2047的佔用2個字節,2個字節的情況下,應當去掉5位(2個字節的最高位和低位字節組的最後3位),還剩11位,能表示的範圍是0到2047。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章