ProtoBuffer的数据格式
1 | message Person { |
上面这条简单的protobuf消息编码后的结果如下
1 | 08 18 12 0a 77 75 6a 69 6e 67 63 68 61 6f 1a 16 77 75 6a 69 6e 67 63 68 61 6f 39 32 40 67 6d 61 69 6c 2e 63 6f 6d |
它的结构是下面这样
1 | 08 // tag (field number 1, wire type 0) |
上面出现的tag和varint是什么,如何得出的?
tag
表示一个属性时首先要求他的tag值:
1 | tag的计算方式: (field_number << 3) | wire_type |
在上面的例子中id的field_number就是1。
每种数据类型都有对应的wire_type:
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 |
3 | Start group groups (deprecated) |
4 | End group groups (deprecated) |
5 | 32-bit fixed32, sfixed32, float |
所以属性id的tag值是
1 | 1 <<< 3 | 0 = 0x08 |
需要注意的是 Length-delimited,如string类型,后面需要跟着的Varint类型表示数据的字节数。
varints
一般情况下int类型都是固定4个字节,protobuf定义了一种变长的int,每个字节最高位表示后面还有没有字节,低7位就为实际的值,并且使用小端的表示方法。因此可以用更少的字节来表示一个int值
例如300,4字节表示为:10 0101100,varint表示为:
1 | 10101100 00000010 |
它的转换过程如下:
ZigZag 编码
负数的最高位为1,如果负数也使用这种方式表示就会出现一个问题,int32总是需要5个字节,int64总是需要10个字节。所以定义了另外一种类型:sint32,sint64。采用ZigZag编码,所有的负数都使用正数表示。
参考:
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html