文章目錄
一、消息結構
【結論先行】:
- 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編碼過程如下】:
- 先將整數轉換爲不帶前導0的二進制表示:
100101100
。 - 每一個字節的低七位存儲數值:
0000010 0101100
。 - 轉換爲小端模式:
0101100 0000010
。 - 添加msb標誌位:
10101100 00000010
。
從上述編碼過程可知,原來需要四個字節表示的整數經過編碼後只需要兩個字節即可存儲。Varints編碼由於使用了msb標誌位,因此可以表示的最大整數爲2^28,所以說Varints編碼基本可以滿足絕大多數的應用場景。
2.1.2 ZigZag編碼
Varints的不足在於對負數進行編碼時效率極低,因爲負數的二進制表示中1特別多,所以沒辦法去掉前導0,所以使用Varints編碼意義不大。ZigZag編碼被用來解決這一問題,其核心思想是將有符號整數轉換爲無符號整數,進而能夠使用Varints編碼。如下圖所示:
以負數-11爲例,其二進制在計算機中是用補碼錶示的,整數原碼爲:00001011
,反碼爲:11110100
,補碼(反碼加1)爲:11110101
。
【ZigZag編碼過程如下】:
- 原數左移一位(左移時丟棄最高位,低位補0):
11101010
。 - 原數右移31位(右移時符號位不變,高位補符號位):
11111111
。 - 將上述兩個二進制數取異或:
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