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 编码如下:

  1. 将二进制分成 7 位一组,从低位开始:1001011000010110000000010
  2. 对每组添加 MSB: 第一组:00101100 → 10101100(MSB = 1,表示有后续字节)。第二组:00000010 → 00000010(MSB = 0,表示这是最后一个字节)。
  3. 最终编码为:10101100 00000010

4. Length-delimited 编码的细节

Length-delimited 类型(如字符串和字节数组)先编码长度,再编码内容:

  1. 长度:使用 Varint 编码表示内容的字节数。
  2. 内容:直接存储实际数据。
示例

字符串 "abc" 的编码:

  1. 字符串长度为 3,用 Varint 编码为 00000011
  2. 字符串内容为 ASCII 编码:61 62 63
  3. 最终编码为: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. 字段 1: Key = 08 → 字段编号 = 1,字段类型 = 0 (Varint)。值 = 01 → 解码为整数 1。
  2. 字段 2: Key = 12 → 字段编号 = 2,字段类型 = 2 (Length-delimited)。长度 = 03 → 内容长度为 3。内容 = 61 62 63 → 解码为字符串 "abc"。

最终解析结果:

{
  "field1": 1,
  "field2": "abc"
}

6. 长度压缩的优势

  • 小整数压缩:Varint 对小整数非常高效。例如,0127 只需 1 个字节。
  • 跳过未识别字段:Length-delimited 类型的长度信息允许解析器快速跳过未知字段。
  • 紧凑的二进制格式:相比 JSON 或 XML,Protobuf 消除了冗余的标签和空格。

总结

Protobuf 的序列化和反序列化通过 Varint 和 Length-delimited 编码实现了高效的压缩和解析。它的核心特点是:

  1. 使用 Varint 压缩整数,减少存储空间。
  2. 使用字段标识符和类型信息实现灵活的消息解析。
  3. 通过长度信息支持快速跳过未知字段。

这种机制使 Protobuf 成为一种高效、灵活的序列化格式,非常适合网络传输和存储。

全部评论

相关推荐

评论
1
2
分享

创作者周榜

更多
牛客网
牛客企业服务