前言
在一個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 1100
和0000 0010
。
- 第一個字節最高位爲1,第二個字節最高位爲0,所以這兩個字節纔是一個完整的數字。
- 將每個字節的最高位去掉,得到:
010 1100
和000 0010
。 - 因爲最低有效組在前面,所以反轉第2步的結果,即
000 0010
和010 1100
。 - 將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。