Protobuf编码详解

Protocol Buffers 二进制序列化协议

什么是Protobuf

Protobuf(Protocol Buffers)是一种数据交换格式,又称PB编码,由Google开源。它类似于JSON、XML,但其内部是纯二进制格式,比JSON、XML等格式要更精炼。Protobuf主要用于数据的序列化和反序列化,目前官方提供了Java、Python、C++等多种语言的实现。

为什么使用Protobuf

  • 体积更小 - 二进制格式,数据传输量更小
  • 解析更快 - 无需解析文本,直接读取二进制
  • 跨语言 - 支持多种编程语言
  • 向后兼容 - 支持字段可选和默认值

数据结构

PB的数据结构是每项数据独立编码,包含一个表示数据类型wire_type和字段序号field_number的数据头,和对应的数据段内容。

HEAD1 + MSG1 + HEAD2 + MSG2 + ...

数据头中数据类型和字段序号的组合方式是:

(field_number << 3) | wire_type

数据类型

常见的数据类型wire_type为0和2:

  • Varint类型(wire_type=0) - 一般用于int数据
  • Length-delimited类型(wire_type=2) - 通常用于字符串、数组等数据

Varint编码

Varint是一种对数字进行编码的方法,将数字编码成不定长的二进制数据,数值越小,编码后的字节越少。

编码规则:

  • 每个字节的最高位表示下一字节是否仍然是编码的内容
  • 若最高位为1,则下一字节仍然是编码的数字的一部分
  • 若该位为0,则编码到本字节结束
  • 每个字节的后7位,由小端表示的数字的二进制值组成

示例:

数值345的Varint编码:0xD9 0x02

数值176的Varint编码:0xB0 0x01

数值24的Varint编码:0x18

ZigZag编码

在PB编码中,为了更高效地处理负数,使用了ZigZag编码,让所有的负数都使用正数表示:

sint32: (n << 1) ^ (n >> 31)
sint64: (n << 1) ^ (n >> 63)

转换示例:

  • 原始值0 → ZigZag值0
  • 原始值-1 → ZigZag值1
  • 原始值1 → ZigZag值2
  • 原始值-2 → ZigZag值3

Length-delimited编码

Length-delimited对可变长度的数据进行编码,将长度和数据编码在一起,类似于TLV结构的LV部分。

示例:

字符串"xieyifenxi"的长度为10,编码为:

0x0a 78 69 65 79 69 66 65 6E 78 69

其中0x0a为长度值的Varint编码,之后是数据内容。

与JSON对比

特性 JSON Protobuf
格式 文本 二进制
可读性
体积 较大 较小
解析速度 较慢 快6倍以上
Schema 无需 需要.proto文件

总结

Protobuf通过使用二进制格式、预定义的数据结构和优化的编码方式,在网络传输和数据处理方面表现出了更高的性能。但JSON在可读性和易用性方面更具优势,适用于Web API开发等场景。

← 什么是JSON JSON与Protobuf对比 →