您的位置:首页 > 其它

protobuf的编码规则

2011-08-25 16:10 351 查看
原文: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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: