protobuf 的 Varint 编码规范
Protocol Buffers(protobuf)中的 Varint(Variable-length integer) 是一种可变长度的整数编码格式,用于高效压缩小整数的存储空间。其核心规范如下:
1. 基本规则
-
目标:用尽可能少的字节表示整数(尤其适合小值)。
-
原理:
-
每个字节的最高位(MSB, Most Significant Bit)是 标志位:
-
1:表示后续还有字节。 -
0:表示当前是最后一个字节。
-
-
每个字节的低 7 位(bits 0-6)存储实际的数值片段,按 小端序(低位在前)组合。
-
2. 编码示例
(1) 单字节(值 ≤ 127)
-
数值
5的二进制:00000101
→ 直接编码为0x05(MSB=0,无需后续字节)。
(2) 多字节(值 > 127)
-
数值
300的编码过程:-
二进制表示:
300 = 1 00101100(9位,需 2 字节)。 -
分割为 7 位一组(小端序):
-
低 7 位:
0101100(0x2C)→ 加上 MSB=1(还有后续字节):10101100(0xAC)。 -
高 2 位:
0000010(0x02)→ 加上 MSB=0(结束):00000010(0x02)。
-
-
最终编码:
[0xAC, 0x02](字节序列)。
-
3. 解码步骤
以字节序列 [0xAC, 0x02] 解码为 300 为例:
-
读取
0xAC(10101100):-
MSB=1 → 还有后续字节。
-
有效数据:
0101100(0x2C)。
-
-
读取
0x02(00000010):-
MSB=0 → 结束。
-
有效数据:
0000010(0x02)。
-
-
组合结果:
-
0x02 << 7 | 0x2C = 0x12C(即300)。
-
4. 特殊规则
(1) 有符号整数(sint32/sint64)
-
先通过 ZigZag 编码 将负数映射到无符号域:
-
0 → 0,-1 → 1,1 → 2,-2 → 3, ... -
公式:
(n << 1) ^ (n >> 31)(32位)。
-
-
再按 Varint 编码。
(2) 固定长度类型(fixed32/fixed64)
-
直接使用固定 4/8 字节存储(不适用 Varint)。
5. 最大字节数
| 类型 | 最大字节数 | 数值范围 |
|---|---|---|
| Varint32 | 5 字节 | -2^31 到 2^31-1 |
| Varint64 | 10 字节 | -2^63 到 2^63-1 |
6. 伪代码实现
编码(Encode Varint32)
def encode_varint(value):
bytes = []
while value > 0x7F:
bytes.append((value & 0x7F) | 0x80)
value >>= 7
bytes.append(value)
return bytes
解码(Decode Varint32)
def decode_varint(buffer):
result = 0
shift = 0
while True:
byte = buffer.read_byte()
result |= (byte & 0x7F) << shift
if not (byte & 0x80):
break
shift += 7
return result
7. 实际应用场景
-
Protobuf 消息长度前缀:
消息头部的长度字段通常用 Varint32 编码。 -
整数字段压缩:
如int32、uint32等字段的存储优化。 -
网络传输:
减少小整数的带宽占用。
8. 为什么用 Varint?
-
空间效率:小整数(如
1、42)仅需 1 字节。 -
兼容性:字段编号(Field Number)和枚举值常用 Varint 编码。
-
流式解析:无需提前知道整数长度。
9. 注意事项
-
性能权衡:
Varint 的解码比固定长度整数稍慢(需位操作和循环)。 -
负数效率:
直接编码负数(如int32)可能占用 5 字节,建议用sint32通过 ZigZag 优化。
总结
Protobuf 的 Varint 编码通过 动态字节长度 和 MSB 标志位 实现了高效的空间压缩,尤其适合小整数和稀疏数据。理解其规范对优化消息大小和解析性能至关重要
浙公网安备 33010602011771号