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对比 →