什么是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开发等场景。