Protobuf 的序列化和反序列化的细节
假设需要传递一个如下所示的messege
syntax = "proto3"; message MyMessage { int32 field1 = 1; // 值为1的int32字段 string field2 = 2; // 值为"abc"的string字段 }
Protocol Buffers (Protobuf) 序列化和反序列化的细节
Protobuf 是一种高效的二进制序列化格式,它通过紧凑的编码方式来减少数据大小。以下是 Protobuf 序列化和反序列化的详细过程,包括长度压缩的机制。
1. Protobuf 的编码规则
Protobuf 使用一种称为 Varint 的编码方式来压缩整数类型,并根据字段类型选择不同的编码方式。以下是常见字段类型的编码规则:
- Varint:用于
int32
,int64
,uint32
,uint64
,bool
等类型。 - Length-delimited:用于
string
,bytes
,repeated
类型。 - Fixed-length:用于
fixed32
,fixed64
,float
,double
类型。
2. 序列化的过程
序列化是将消息对象转换为紧凑的二进制格式的过程。以下是序列化的详细步骤:
(1) 字段的编码
每个字段在序列化时由以下两部分组成:
- 字段标识符 (Key):包含字段编号和字段类型。
- 字段值 (Value):字段的实际数据。
字段标识符 (Key)
字段标识符是一个 Varint,包含以下信息:
- 字段编号 (Field Number):在
.proto
文件中定义的字段编号。 - 字段类型 (Wire Type):表示字段值的编码方式。
字段标识符的计算公式:
Key = (Field Number << 3) | Wire Type
0 | Varint |
1 | 64-bit 固定长度 |
2 | Length-delimited |
5 | 32-bit 固定长度 |
示例
假设字段编号为 1
,字段类型为 Varint
(Wire Type = 0),则:
Key = (1 << 3) | 0 = 8
(2) 字段值的编码
字段值的编码方式取决于字段类型:
- Varint 编码:整数类型使用 Varint 编码。
- Length-delimited 编码:字符串和字节数组先编码长度,再编码内容。
- Fixed-length 编码:浮点数和固定长度整数直接按固定字节数存储。
3. Varint 编码的细节
Varint 是一种变长整数编码方式,用于压缩整数。它的核心思想是:
- 每个字节的最高位(MSB)表示是否有后续字节。 如果 MSB = 1,表示还有后续字节。如果 MSB = 0,表示这是最后一个字节。
- 剩余 7 位用于存储实际数据。
示例
整数 300
的二进制表示为 100101100
,用 Varint 编码如下:
- 将二进制分成 7 位一组,从低位开始:
100101100
→00101100
和00000010
。 - 对每组添加 MSB: 第一组:00101100 → 10101100(MSB = 1,表示有后续字节)。第二组:00000010 → 00000010(MSB = 0,表示这是最后一个字节)。
- 最终编码为:
10101100 00000010
。
4. Length-delimited 编码的细节
Length-delimited 类型(如字符串和字节数组)先编码长度,再编码内容:
- 长度:使用 Varint 编码表示内容的字节数。
- 内容:直接存储实际数据。
示例
字符串 "abc"
的编码:
- 字符串长度为
3
,用 Varint 编码为00000011
。 - 字符串内容为 ASCII 编码:
61 62 63
。 - 最终编码为:
03 61 62 63
。
5. 反序列化的过程
反序列化是将二进制数据解析为消息对象的过程。以下是反序列化的详细步骤:
(1) 解析字段标识符
从二进制数据中读取 Varint,解析字段编号和字段类型:
- 字段编号:
Key >> 3
- 字段类型:
Key & 0x07
(2) 解析字段值
根据字段类型解析字段值:
- Varint:逐字节读取,直到 MSB = 0。
- Length-delimited:先读取长度(Varint 编码),再读取内容。
- Fixed-length:直接读取固定字节数。
示例
假设接收到的二进制数据为:08 01 12 03 61 62 63
,解析过程如下:
- 字段 1: Key = 08 → 字段编号 = 1,字段类型 = 0 (Varint)。值 = 01 → 解码为整数 1。
- 字段 2: Key = 12 → 字段编号 = 2,字段类型 = 2 (Length-delimited)。长度 = 03 → 内容长度为 3。内容 = 61 62 63 → 解码为字符串 "abc"。
最终解析结果:
{ "field1": 1, "field2": "abc" }
6. 长度压缩的优势
- 小整数压缩:Varint 对小整数非常高效。例如,
0
到127
只需 1 个字节。 - 跳过未识别字段:Length-delimited 类型的长度信息允许解析器快速跳过未知字段。
- 紧凑的二进制格式:相比 JSON 或 XML,Protobuf 消除了冗余的标签和空格。
总结
Protobuf 的序列化和反序列化通过 Varint 和 Length-delimited 编码实现了高效的压缩和解析。它的核心特点是:
- 使用 Varint 压缩整数,减少存储空间。
- 使用字段标识符和类型信息实现灵活的消息解析。
- 通过长度信息支持快速跳过未知字段。
这种机制使 Protobuf 成为一种高效、灵活的序列化格式,非常适合网络传输和存储。