protobuf編碼和存儲方式詳解

一、消息結構

結論先行】:

  • protobuf將消息裏的每個字段進行編碼後,再利用T-L-V或者T-V的方式進行數據存儲
  • protobuf對於不同類型的數據會使用不同的編碼和存儲方式
  • protobuf的編碼和存儲方式是其性能優越、數據體積小的原因

  protobuf中每一個消息中的字段都是key-value類型,序列化的時候key-value被編碼和存儲爲字節流整體的消息結構如下圖所示:
在這裏插入圖片描述
Tag 由字段編號 field_number 和 編碼類型 wire_type 組成, Tag 整體採用 Varints 編碼。Tag的值爲(field_number << 3) | wire_type,即最後三位存儲wire type(編碼類型),其他位用於存儲field number(字段編號)。
在這裏插入圖片描述
wire type編碼有以下幾種類型:
在這裏插入圖片描述
【T-L-V格式】:

  • T 作爲標識號表明了在message中的位置以及數據類型。

  • L 定義了value的長度,表示可變長度的字段。

  • V 表示實際的value。

二、Wire Type = 0時的編碼和存儲方式

  結論先行】:對於int32/int64類型的數據(正數),protobuf會使用Varints編碼;而對於sint32/sint64類型的數據(負數),protobuf會先使用ZigZag 編碼,再使用Varints編碼存儲格式爲Tag-Value

2.1 編碼方式

2.1.1 Varints編碼

  Varints是使用一個或多個字節序列化整數的一種方法,也是一種壓縮算法在Varints編碼方式中,數字越小,佔用的字節越少。Varints編碼的主要依據是:越小的數字出現的頻率越高

  通常我們要存儲一個整數需要4個字節或者8個字節,例如對於32位整數300,其二進制表示爲:00000000 00000000 00000001 00101100。可以看到二進制表示中有很多無用的0也佔用了內存,因此可以使用Varints編碼對其進行壓縮和解碼。

  Varints編碼使用msb表示當前是否是最後一個字節,如果msb爲1表示後面還有字節,如果msb爲0表示當前字節是最後一個字節。因爲加入了msb,所以每一個字節低七位表示具體的數值

Varints編碼過程如下】:

  1. 先將整數轉換爲不帶前導0的二進制表示:100101100
  2. 每一個字節的低七位存儲數值:0000010 0101100
  3. 轉換爲小端模式:0101100 0000010
  4. 添加msb標誌位:10101100 00000010

  從上述編碼過程可知,原來需要四個字節表示的整數經過編碼後只需要兩個字節即可存儲。Varints編碼由於使用了msb標誌位,因此可以表示的最大整數爲2^28,所以說Varints編碼基本可以滿足絕大多數的應用場景。

2.1.2 ZigZag編碼

  Varints的不足在於對負數進行編碼時效率極低,因爲負數的二進制表示中1特別多,所以沒辦法去掉前導0,所以使用Varints編碼意義不大。ZigZag編碼被用來解決這一問題,其核心思想是將有符號整數轉換爲無符號整數,進而能夠使用Varints編碼如下圖所示:
在這裏插入圖片描述
  以負數-11爲例,其二進制在計算機中是用補碼錶示的,整數原碼爲:00001011,反碼爲:11110100,補碼(反碼加1)爲:11110101

ZigZag編碼過程如下】:

  1. 原數左移一位(左移時丟棄最高位,低位補0):11101010
  2. 原數右移31位(右移時符號位不變,高位補符號位):11111111
  3. 將上述兩個二進制數取異或:00010101

  ZigZag編碼和解碼過程用代碼表示爲:

int int_to_zigzag(int n)
{
	return (n <<1) ^ (n >>31);
}

int zigzag_to_int(int n) 
{
	return (((unsignedint)n) >>1) ^ -(n & 1);
}

2.2 存儲方式

  protobuf使用Varints和ZigZag編碼後,以T-V格式存儲數據,即:
在這裏插入圖片描述

三、Wire Type = 2時的編碼和存儲方式

  結論先行】:對於string,bytes和嵌套消息類型的數據,protobuf會使用Length-delimited編碼存儲格式爲Tag-Length-Value

3.1 編碼方式

  當時value的類型爲string,bytes和嵌套消息時,protobuf使用Length-delimited編碼,即將value的length也編碼進最終數據。
例如:

message Test2 {
  required string b = 2;
}
// 設置b的值爲"testing",則編碼之後爲:
12 07 74 65 73 74 69 6e 67

在這裏插入圖片描述

3.2 存儲方式

  使用Length-delimited編碼時的數據存儲方式爲T-L-V,即:
在這裏插入圖片描述

四、Wire Type = 1&5時的編碼和存儲方式

  結論先行】:對於大整數類型的數據,protobuf會使用64-bit和32-bit編碼方式存儲格式爲Tag-Value

4.1 編碼方式

  Varints適合處理一定範圍內的數字,當數字很大的時候使用Varints編碼效率反而很低,因此protobuf定義了64-bit和32-bit兩種定長編碼類型,即:
在這裏插入圖片描述

4.2 存儲方式

  protobuf使用64-bit和32-bit編碼後,以T-V格式存儲數據

參考:
https://developers.google.cn/protocol-buffers/docs/encoding
https://www.bilibili.com/video/BV1Bt411X73L/?spm_id_from=333.788.videocard.19
https://www.cnblogs.com/mler/p/10252886.html
https://www.jianshu.com/p/30ef9b3780d9
https://www.jianshu.com/p/6dd2fd0362b8

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