protobuf的编码规则

来源:互联网 发布:淘宝图片制作视频教程 编辑:程序博客网 时间:2024/05/21 22:56
http://blog.csdn.net/goldenfish1919/article/details/6718820
 

protobuf的编码规则

分类: protobuf 536人阅读 评论(1) 收藏 举报
原文:http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/encoding.html

看一个简单的mesage:
message Test1 {
  required int32 a = 1;
}
如果应用程序创建了一个Test1的对象,并把a赋值为150,那么protobuf会把它编码成这样三个字节:08 96 01。
这三个字节代表什么含义呢?

Base 128 Varints
protobuf使用一种叫做“Base 128 Varints”的编码方式,它使用一个或者多个字节来序列化一个整数,数字越小使用的字节数就越少。
具体来说就是:
除了最后一个字节之外的每一个字节的最高位置1,代表还没结束,后面还有更多的字节,最后一个字节的最高位置0,表明结束,
每个字节剩下的7位,是以二进制的补码,低字节序( least significant group first)的形式表示。
以数字1为例,二进制是0000 0001,最高位是0代表后面没有更多的字节,剩下的7位代表1。
以数字300为例,它在“Base 128 Varints”规则下的表示形式是:1010 1100 0000 0010
第一个字节是 1010 1100,最高位是1,代表后面还有更多的字节,因此,第一个字节的内容是 010 1100
第二个字节是 0000 0010,最高位是0,代表后面没有更多的字节,因此,第二个字节的内容是 000 0010
因为是采用“低字节序”因此,实际的字节是:000 0010 010 1100 = 1 0010 1100 = 256 + 32 + 8 + 4 = 300

message的结构
protobuf的message是一连串的key/value对,二进制形式的message也是用key/value的形式,只不过,二进制形式的message的key是message里面的序号,value是message里面key和value的组合。接收端只有通过参考.proto才可以正确的解码。

当message被编码的时候,key和value一块写到二进制流里面,解码的时候,解析器能够跳过那些他不认识的field,用这种方式,新的filed可以添加到那些旧的不认识这些字段的程序的message里面,二进制形式的key实际上有两个部分组成,一部分是.proto文件里面的序号,另一部分是传输类型(wire type)。
传输类型:
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 
-----------------------------------------------------------------------------------
3   | Start group           | groups (deprecated) 
-----------------------------------------------------------------------------------
4   | End group            | groups (deprecated) 
-----------------------------------------------------------------------------------
5   | 32-bit                    |  fixed32, sfixed32, float 
-----------------------------------------------------------------------------------
二进制的key都是varint,值是field_number << 3) | wire_type,也就是说,后三位存储的是传输类型,前5位存储的是序号。
下面我们来看一下最开始的那个例子:08 96 01,因为在二进制流里面的第一个数字都是key,因此key就是08,二进制:0000 1000,
后三位是000,代表传输类型是0,前面的5位是0000 1就是数字1,代表在.proto文件里面的序号是1.
因此,通过传输类型知道后面传输的是Varint,通过序号知道在message里面的tag是1.
使用Base 128 Varints的解码方式,解码96 01,二进制:1001 0110  0000 0001
第一个字节是 1001 0110,最高位是1,代表后面还有更多的字节,因此,第一个字节的内容是 001 0110
第一个字节是 0000 0001,最高位是0,代表后面没有更多的字节,因此,第二个字节的内容是 000 0001
因为是采用“低字节序”因此,实际的字节是:000 0001 001 0110 = 1001 0110 = 128 + 16 + 4 + 2 = 150

更多的值的类型

有符号的整数
使用Base 128 Varints方式编码负数的时候,有符号的sint32,sint63 跟 标准的int32,int64有很大的不同。当负数用int32,int64表示的时候,varint编码以后总是占10个字节的长度,它是把这个负数当成了一个很大的正数来对待的,如果用sint32,sint64来表示的话,结果会用更高效的ZigZag方式进行编码。
ZigZag编码方式把有符号的整数映射成无符号的整数,因此,绝对值小的整数编码以后占的字节数就少,具体的编码规则:
对于sint32:(n << 1) ^ (n >> 31)
对于sint64:(n << 1) ^ (n >> 63)
注意:n右移31或63位以后就变为全0(正数)或全1(负数)

非变长的数字
double和fixed64的传输类型是1,使用固定的64位,float和fixed32的传输类型是5,使用固定的32位。都是使用低字节序(little-endian)。

字符串
传输类型是2代表:value使用varint编码的,传输类型后面紧跟的就是value所占的字节数。比如:
message Test2 {
  required string b = 2;
}
如果b的值是testing,编码以后就是:12 07 74 65 73 74 69 6e 67
key是12(16进制的数值): 0001 0010,后三位010,是2,代表传输类型是2,前5位是2,代表序号是2.
长度是07,代表value的长度是7
74(16进制的数值):代表的就是116,字母t。

message嵌套
现在有一个message,嵌套了message Test1:
message Test3 {
  required Test1 c = 3;
}
编码以后是: 1a 03 08 96 01
1a:0001 1010,后三位是010,传输类型是2.前5位是3,代表序号是3。
03:代表value的长度是3
08 96 01:跟Test1的表示方式是一样的。

可选和重复字段
如果message的字段是repeated,但是没有使用[pached=true]选项,编码以后的message就会有0个或者多个有相同tag的key/value对,这些相同tag的key/value对不需要连续出现,可以跟其他的字段交错,但是需要保持这些元素本身的前后顺序。
如果message的字段是repeated,编码以后可能出现也可能不出现。
一般情况下,编码以后的message的位二进制流里面,一个optional和required字段不应该出现多次,如果出现了,对于数字和字符串类型,只接受最后一个,对于嵌套message的字段,会合并多个实例的相同字段,就如同使用 Message::MergeFrom方法一样。具体来说:后面出现的标量field会替换前面出现的,嵌入的message继续合并,repeated字段会进行拼接。这么做的结果就是:拼接编码以后的message等价于先把message拼接然后再编码。

紧凑的重复字段
使用[packed=true]选项的repeated字段。0个元素的field不出现在编码以后的message里面,多个元素的field,会把所有的元素编码进单个的传输类型是2的key/value对里面。
每一个元素都按照普通的方式进行编码,只是没有前导的tag。比如说:
message Test4 {
  repeated int32 d = 4;
}
假如现在有一个Test4的对象,d的值是3, 270,86942。编码以后是:
22        // tag (field number 4, wire type 2)
06        // payload size (6 bytes)
03        // first element (varint 3)
8E 02     // second element (varint 270)
9E A7 05  // third element (varint 86942):
22:10 0010,后三位是010,传输类型是2,前5位是100,tag是4。
06:代表长度
03:3
8E 02:1000 1110 0000 0010,实际的值是:1 0000 1110,就是256 + 8 + 4 + 2 = 270
9E A7 05:1001 1110 1010 0111 0000 0101,实际值是:10101001110011110,就是:86942
[注意]只有基本的数字类型(varint, 32-bit, 或者64-bit) 才可以声明成packed。

原创粉丝点击