Protobuf 的序列化和反序列化的細(xì)節(jié)
假設(shè)需要傳遞一個如下所示的messege
syntax = "proto3"; message MyMessage { int32 field1 = 1; // 值為1的int32字段 string field2 = 2; // 值為"abc"的string字段 }
Protocol Buffers (Protobuf) 序列化和反序列化的細(xì)節(jié)
Protobuf 是一種高效的二進(jìn)制序列化格式,它通過緊湊的編碼方式來減少數(shù)據(jù)大小。以下是 Protobuf 序列化和反序列化的詳細(xì)過程,包括長度壓縮的機(jī)制。
1. Protobuf 的編碼規(guī)則
Protobuf 使用一種稱為 Varint 的編碼方式來壓縮整數(shù)類型,并根據(jù)字段類型選擇不同的編碼方式。以下是常見字段類型的編碼規(guī)則:
- Varint:用于
int32
,int64
,uint32
,uint64
,bool
等類型。 - Length-delimited:用于
string
,bytes
,repeated
類型。 - Fixed-length:用于
fixed32
,fixed64
,float
,double
類型。
2. 序列化的過程
序列化是將消息對象轉(zhuǎn)換為緊湊的二進(jìn)制格式的過程。以下是序列化的詳細(xì)步驟:
(1) 字段的編碼
每個字段在序列化時由以下兩部分組成:
- 字段標(biāo)識符 (Key):包含字段編號和字段類型。
- 字段值 (Value):字段的實(shí)際數(shù)據(jù)。
字段標(biāo)識符 (Key)
字段標(biāo)識符是一個 Varint,包含以下信息:
- 字段編號 (Field Number):在
.proto
文件中定義的字段編號。 - 字段類型 (Wire Type):表示字段值的編碼方式。
字段標(biāo)識符的計(jì)算公式:
Key = (Field Number << 3) | Wire Type
0 | Varint |
1 | 64-bit 固定長度 |
2 | Length-delimited |
5 | 32-bit 固定長度 |
示例
假設(shè)字段編號為 1
,字段類型為 Varint
(Wire Type = 0),則:
Key = (1 << 3) | 0 = 8
(2) 字段值的編碼
字段值的編碼方式取決于字段類型:
- Varint 編碼:整數(shù)類型使用 Varint 編碼。
- Length-delimited 編碼:字符串和字節(jié)數(shù)組先編碼長度,再編碼內(nèi)容。
- Fixed-length 編碼:浮點(diǎn)數(shù)和固定長度整數(shù)直接按固定字節(jié)數(shù)存儲。
3. Varint 編碼的細(xì)節(jié)
Varint 是一種變長整數(shù)編碼方式,用于壓縮整數(shù)。它的核心思想是:
- 每個字節(jié)的最高位(MSB)表示是否有后續(xù)字節(jié)。 如果 MSB = 1,表示還有后續(xù)字節(jié)。如果 MSB = 0,表示這是最后一個字節(jié)。
- 剩余 7 位用于存儲實(shí)際數(shù)據(jù)。
示例
整數(shù) 300
的二進(jìn)制表示為 100101100
,用 Varint 編碼如下:
- 將二進(jìn)制分成 7 位一組,從低位開始:
100101100
→00101100
和00000010
。 - 對每組添加 MSB: 第一組:00101100 → 10101100(MSB = 1,表示有后續(xù)字節(jié))。第二組:00000010 → 00000010(MSB = 0,表示這是最后一個字節(jié))。
- 最終編碼為:
10101100 00000010
。
4. Length-delimited 編碼的細(xì)節(jié)
Length-delimited 類型(如字符串和字節(jié)數(shù)組)先編碼長度,再編碼內(nèi)容:
- 長度:使用 Varint 編碼表示內(nèi)容的字節(jié)數(shù)。
- 內(nèi)容:直接存儲實(shí)際數(shù)據(jù)。
示例
字符串 "abc"
的編碼:
- 字符串長度為
3
,用 Varint 編碼為00000011
。 - 字符串內(nèi)容為 ASCII 編碼:
61 62 63
。 - 最終編碼為:
03 61 62 63
。
5. 反序列化的過程
反序列化是將二進(jìn)制數(shù)據(jù)解析為消息對象的過程。以下是反序列化的詳細(xì)步驟:
(1) 解析字段標(biāo)識符
從二進(jìn)制數(shù)據(jù)中讀取 Varint,解析字段編號和字段類型:
- 字段編號:
Key >> 3
- 字段類型:
Key & 0x07
(2) 解析字段值
根據(jù)字段類型解析字段值:
- Varint:逐字節(jié)讀取,直到 MSB = 0。
- Length-delimited:先讀取長度(Varint 編碼),再讀取內(nèi)容。
- Fixed-length:直接讀取固定字節(jié)數(shù)。
示例
假設(shè)接收到的二進(jìn)制數(shù)據(jù)為:08 01 12 03 61 62 63
,解析過程如下:
- 字段 1: Key = 08 → 字段編號 = 1,字段類型 = 0 (Varint)。值 = 01 → 解碼為整數(shù) 1。
- 字段 2: Key = 12 → 字段編號 = 2,字段類型 = 2 (Length-delimited)。長度 = 03 → 內(nèi)容長度為 3。內(nèi)容 = 61 62 63 → 解碼為字符串 "abc"。
最終解析結(jié)果:
{ "field1": 1, "field2": "abc" }
6. 長度壓縮的優(yōu)勢
- 小整數(shù)壓縮:Varint 對小整數(shù)非常高效。例如,
0
到127
只需 1 個字節(jié)。 - 跳過未識別字段:Length-delimited 類型的長度信息允許解析器快速跳過未知字段。
- 緊湊的二進(jìn)制格式:相比 JSON 或 XML,Protobuf 消除了冗余的標(biāo)簽和空格。
總結(jié)
Protobuf 的序列化和反序列化通過 Varint 和 Length-delimited 編碼實(shí)現(xiàn)了高效的壓縮和解析。它的核心特點(diǎn)是:
- 使用 Varint 壓縮整數(shù),減少存儲空間。
- 使用字段標(biāo)識符和類型信息實(shí)現(xiàn)靈活的消息解析。
- 通過長度信息支持快速跳過未知字段。
這種機(jī)制使 Protobuf 成為一種高效、靈活的序列化格式,非常適合網(wǎng)絡(luò)傳輸和存儲。