protobuf底层编码格式一览

protobuf底层编码格式

首先要认识protobuf支持的几种编码方式和它们对应的数据类型:

wire-typemeaningused for
0varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2length-delimitedstring, bytes, embedded messages, packed repeated fields
532-bitfixed32, sfixed32, float

值为3和4的wire-type分别是start group和end group,它们现在已被废弃

protobuf使用的编码方式可以简记为TLV:Tag + (Length +) Value。

Tag的定义是:field_num << 3 | wire_type,这个field_num就是.proto文件中字段的编号。

举个例子,如果你有一个.proto文件如下:

message pod {
	optional int32 a = 1;
	optional string str = 2;
	repeated sint32 vec = 3;
}

那么三个字段编码后的tag如下(二进制格式):

a: 00001 000 // field_num = 1, wire_type = 0
str: 00010 010 // field_num = 2, wire_type = 2
vec: 00010 011 // field_num = 3, wire_type = 2

说完了tag,下面我们来说说不同wire-type的实际编码方式。

varint

varint即可变长度编码,对于值较小的整数,它比定长编码花费更少的字节,值较大的整数则花费更多字节,因为大多整数值都比较小,所以一般可变长度编码的空间效率要优于定长编码。

varint是一个字节或连续的几个字节,每个字节的最高位是标志位,如果标志位为1,表示该字节后面还有其他字节,如果标志位为0,表示这是最后一个字节。

可变长度编码中每个字节只有7个位能用来保存值,并且它们是按小端序存储的

仍使用上面的.proto文件,对不同的a值,它对应的编码如下:

a = 0x10, code = 00001000 00010000
a = 0xfe, code = 00001000 11111110 00000001
a = 0x7ffffffe, code = 00001000 11111110 11111111 11111111 11111111 00000111

对a = 0x7f ff ff fe,它的可变长度编码用了5个字节,这多于定长编码所用字节数

要解码多字节varint的值,我们首先取出每个字节的低7位,每低7位为一个整体按字节顺序从后往前依次摆放。在这个例子中,首先摆出第5个字节的低7位0000111,然后依次摆出第4、3、2、1字节的低7位,最后得到:0000111 1111111 1111111 1111111 1111110,截断前面多余的0后得到01111111 11111111 11 111111 11111110,正是0x7f ff ff fe

得到实际的整数值后,protobuf还需要判断是否是sint32/sint64类型,这一点可以直接从.proto文件的类型声明中获知:

  • 如果不是sint32/sint64类型,直接将解析出的整数值赋给即可。
  • 如果是sint32/sint64类型,那么还需要一步转换。

为什么要对sint32/sint64类型进行转换呢?因为对于较小的负数例如-1,它的二进制表示是0xffffffff ,如果直接使用varint编码将花费5个字节,所以protobuf为了使用更少的字节表示较小的负数实现了ZigZag编码

Signed OriginalEncoded As
00
-11
12
-23
…………
21474836474294967294
-21474836484294967295

ZigZag编码只对sint32/sint64类型有效!它是一个加在varint编解码和整数生成之间的额外步骤:

sint型整数 <-> ZigZag编解码 <-> varint编解码 <-> varint整数

32-bit / 64-bit

定长编码的格式非常简单:Tag + Value(4 bytes / 8 bytes)。

例如对于下面的.proto文件:

message ints {
	optional fixed32 n = 1;
	optional fixed64 m = 2;
}

当n = 0x10121314, m = 0x10121314时,其编码如下:

n: 00001101 00010100 00010011 00010010 00010000
m: 00010001 00001000 00000111 00000110 00000101 00000100 00000011 00000010 00000001

Value部分同样按照小端法编码,这和主流PC的内部整数表示一致。

length-delimited

使用完整的TLV格式,Length部分使用varint编码,表示负载数据的长度。

例如对于下面的.proto文件:

message Ldls {
  optional string str = 1;
  repeated int32 vec = 2;
}

当str的值为"hello",vec的内容为{0x11, 0xfe}时,其编码如下:

str: 00001010 000001015字节) [字符串"hello"]01101000 01100101 01101100 01101100 01101111
vec: 00010010 000000113字节) [第一个数字0x11]00010001 [第二个数字0xfe]11111110 00000001

值得一提的是,嵌套的消息类型也使用length-delimited格式编码。

例如对于下面的.proto文件:

message aInt {
  optional int32 a = 1;
}

message complexType {
  optional aInt b = 2;
}

当a取值为0x1时,complexType对象编码为:

b: 00010010 000000102字节) [a标识]00001000 [数字a的内容]00000001
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值