Protobuf编码规则详解

1 Message 结构

Message由一系列field组成,每个字段都使用TLV(Tag-Length-Value)结构形式。每个字段都有一个 tag 值,length 表示 value 数据编码后的长度,length 不是必须的,对于固定长度的和使用Varint编码的 value,是没有 length 的。value 是数据本身的内容。
在这里插入图片描述

1.1 tag

  • tag 有 field_number 和 wire_type 两部分组成,组成格式:field_num << 3 | wire_type
  • tag使用Varint编码
  • tag是要占空间的,如果tag>16时,KEY的编码就会占用2个字节了

结构如下图
在这里插入图片描述

1.1.1 字段编号(field_num)

就是.proto文件中定义的字段编号

1.1.2 传输类型(wire_type)

每个字段都有一个对应的字段(传输)类型,如下表:

Type Meaning Used For Structure value的字节序 value编码格式 Length
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum Tag-Value 小端字节序 Varint编码 变长 无Length值
1 64-bit fixed64, sfixed64, double Tag-Value 小端字节序 非Varint编码 固定8字节
2 Length-delimited string, bytes, embedded messages, packed repeated fields Tag-Length-Value 变长,有Length值
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float Tag-Value 小端字节序 非Varint编码 固定4字节
  • 消息的二进制格式只使用消息字段的字段编号(field_num)和write_type(根据proto文件定义的类型对应而来)作为Tag的一部分,字段名和声名类型只能在解析端通过引用参考消息类型的定义(即.proto文件)才能确定。
  • 解码的时候解码程序(解码器)读入二进制的字节流,解析出每一个field;如果解码过程中遇到识别不出来的filed_num就直接跳过。这样的机制保证了即使该消息(message)添加了新的字段,也不会影响旧的编/解码程序正常工作。

1.2 字段顺序

字段编号可以在 .proto 文件中以任何顺序使用,编码 / 解码与字段顺序无关。
序列化 message 时,对于如何写入其已知字段或未知字段没有保证的顺序。解析消息不能认为filed_num=1 的消息一定在最前。

1.3 默认值

编码时如果没有对字段设置值,protobuf就不会把该字段编码到消息中。
解析数据时,如果编码的消息不包含特定的字段,则解析将对象中的相应字段将设置为该字段的默认值
不同类型的默认值不同,具体如下:

  • 对于字符串,默认值为null
  • 对于字节,默认值为空字节
  • 对于bool,默认值为false
  • 对于数字类型,默认值为零
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • repeated字段默认值是空列表
  • message字段的默认值为空对象

2 编码

2.1 Varint编码

Varint是一种将一个整数序列化为一个或者多个Byte的方法。越小的整数,使用的Bytes越少。Varint规则如下

  • 每个Byte的最高位是标志位(msb, most significant bit)。如果值为1,表示该Bytes后面还有其他Byte;如果该位为0,表示该Byte是最后一个Byte。
  • 每个Byte的低7位是用来存数值的位。
  • Varint方法使用小端字节序(低位在前编码),通常都是大端字节序(高位在前)

2.1.1 Varint编码过程

步骤/值(10进制) 65 128
1 大端字节序二进制(低位在后/右) 1000001 10000000
2 7位一分隔 (从低开始计数分隔) 1000001 0000001,0000000
3 补标志位 01000001,后边高位没1了,标志位补0 00000001(后边高位没1了,标志位补0),10000000(后边高位有1标志位补1)
4 翻转变为小端字节序(低位在前/左) 01000001 10000000 00000001

2.1.2解码过程

Varints 的解码就是对编码的逆操作

10进制数字 65 128
1 pb编码值(小端字节序,低位在前/左),从低位(左)8位一分格 01000001 10000000 00000001
2 去补标志位(最高位) 1000001 0000000 0000001
3 翻转为大端字节序(低位在后/右) 1000001 0000001 0000000

2.1.3 存储

一个字节的 Varints 编码有 7 位可以存储数据(最高位为 msb),则可以传输 [ 0 , 2^7 -1] 以此类推,两个字节就是 [ 2 ^7 , 2^14 − 1 ]

2.1.4 小结

Varint 确实是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。比如对于 int32 类型的数字,一般需要 4 个字节来表示。但是采用 Varints,对于很小的 int32 类型的数字,则可以用 1 个字节来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个字节来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。如果确定传输大的数字,可以考虑fixed32/fixed64 类型

2.2 有符号整数(sint32和sint64)编码的问题与zigzag优化

protocol buffer中 write_type=0 的都使用Varint编码。当数值为负数时,有符号整型(sint32, sint64)和标准整型(int32, int64)有一个重要的差别。如果使用 int32 或 int64 存储负数,那么 Varints 编码后的结果一定是 10 个字节(int32 类型的负数也是占用10个字节)。而如果使用 sint32 或 sint64 存储负数,则会使用效率更高的 ZigZag 编码。
为此 Protobuf 定义了 sint32 和 sint

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值