赞
踩
首先要认识protobuf支持的几种编码方式和它们对应的数据类型:
wire-type | meaning | used for |
---|---|---|
0 | varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | length-delimited | string, bytes, embedded messages, packed repeated fields |
5 | 32-bit | fixed32, 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是一个字节或连续的几个字节,每个字节的最高位是标志位,如果标志位为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类型进行转换呢?因为对于较小的负数例如-1
,它的二进制表示是0xffffffff
,如果直接使用varint编码将花费5个字节,所以protobuf为了使用更少的字节表示较小的负数实现了ZigZag编码:
Signed Original | Encoded As |
---|---|
0 | 0 |
-1 | 1 |
1 | 2 |
-2 | 3 |
…… | …… |
2147483647 | 4294967294 |
-2147483648 | 4294967295 |
ZigZag编码只对sint32/sint64类型有效!它是一个加在varint编解码和整数生成之间的额外步骤:
sint型整数 <-> ZigZag编解码 <-> varint编解码 <-> varint整数
定长编码的格式非常简单: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的内部整数表示一致。
使用完整的TLV格式,Length部分使用varint编码,表示负载数据的长度。
例如对于下面的.proto文件:
message Ldls {
optional string str = 1;
repeated int32 vec = 2;
}
当str的值为"hello",vec的内容为{0x11, 0xfe}时,其编码如下:
str: 00001010 00000101(5字节) [字符串"hello"]01101000 01100101 01101100 01101100 01101111
vec: 00010010 00000011(3字节) [第一个数字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 00000010(2字节) [a标识]00001000 [数字a的内容]00000001
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。