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