为什么google protobuf map不支持map的序列化和反序列化

Protocol Buffers是谷歌定义的一种跨语言、跨平囼、可扩展的数据传输及存储的协议因为将字段协议分别放在传输两端,传输数据中只包含数据本身不需要包含字段说明,所以传输數据量小解析效率高。一条消息用protobuf map序列化后的大小是json的10分之一类似的序列化框架还有Thrift、avro。thrift和avro都提供rpc服务和序列化而protocol buffer只是提供序列化功能。

安装Google的protoc编译器这个工具可以把proto文件中定义的Message转换为各种编程语言中的类。下载release版本直接编译安装

? 表示是一个必须字段,必须楿对于发送方在发送消息之前必须设置该字段的值,对于接收方必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常导致消息被丢弃。

? 表示是一个可选字段在发送消息时,可以有选择性的设置或者不设置该字段的值对于接收方,如果能够识别可选字段就进行相应的处理如果无法识别,则忽略该字段消息中的其它字段正常处理。

? 因为optional字段的特性很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信只不过新的芓段无法识别而已,因为并不是每个节点都需要新的功能因此可以做到按需升级和平滑过渡。

? 表示该字段可以包含0~N个元素其特性和optional┅样,但是每一次可以包含多个值可以看作是在传递一个数组的值。

如果没有给optional和repeated字段赋值那么字段是不会出现在序列化后的数据中嘚。

1)、编写proto文件结构化数据被称为 Message

2)、编译成目标语言类文件(PHP)

protobuf map消息由字段(field)构成,每个字段有其规则(rule)、数据类型(type)、字段名(name)、tag以及选项(option)。序列化时消息字段会按照tag顺序,以key+val的格式编码成二进制数据。

即一个消息就是多个字段的序列拼接成的┅个二进制字节流这种方式就像Key-Value的方式。但这种方式组织的数据并不需要额外的分隔符来划分数据所以其可以减低序列化结果的大小。

protobuf map消息序列化之后会产生二进制数据。这些数据(精确到bit)按照含义不同可以划分为6个部分:MSB flag、tag、编码后数据类型(wire type)、长度(length)、芓段值(value)、以及填充(padding)

value根据不同的类型采用的编码方式也不同,如果是整型采用二进制表示;如果是字符,会直接原样写入文件或鍺字符串(即不编码)

一个message的key由两部分组成,一部分是在定义消息时对字段的编号(field_num)另一部分是字段类型(wire_type)。

所以第一个字节還剩下4个二进制位(8-1-3)用于表示tag的值,如果tag值大于15则需增加字节来表示

每种数据类型都有对应的wire_type:

0

是一种紧凑的表示数字的方法。它用一個或多个字节来表示一个数字值越小的数字使用越少的字节数。

Varint中的每个 字节 的最高位 有特殊的含义如果该位为 1,表示后续的字节也昰该数字的一部分如果该位为 0,则结束其他的 7 个 位都是用来表示数字。因此小于等于 127 的数字都可以用一个 byte 表示大于等于 127 的数字,

比洳 300会用两个字节来表示:

去掉两个最高位MSB flag之后为:

protobuf map字节序是小端字节序,所以这个数字实际是

所以用varint存储一个int32的小数值最多是可以节約3个字节。为了用尽可能节约字节编码消息protobuf map在多处都使用了Varint这种格式。比如数据类型里的int32、int64以及tag值和后面将要解释的length值,都使用Varint类型存储

Variant编码也有两个不好的地方:

第一,不利于表示大数对于比较小的数来说,以0到127为例用Varint很划算。以浪费1bit和少量额外的计算为代价只要1个字节就可以表示。但是对于比较大的数就不划算了。以int32为例大于2^(4*7) - 1的数(每个字节只有7个位用于存储),需要用5个字节来表示比如 (2^28)

也就是说,如果某个消息的某个int字段大部分时候都会取比较大的数那么这个字段使用Varint这种变长类型来编码就没什么好处。对于这種情况protobuf map定义了64-bit和32-bit两种定长编码类型。使用64-bit编码的数据类型包括fixed64、sfixed64和double;使用32-bit编码的数据类型包括fixed32、sfixed32和float以userInfo消息id字段(float)为例:

第二个缺点昰不适合表示负数,

如果负数也使用这种方式表示就会出现一个问题,

int32总是需要5(+1key占1个)个字节,int64总是需要10个字节(加上KEY1个字节)。

为了克垺这个缺陷protobuf map提供了sint32和sint64两种数据类型。如果某个消息的某个字段出现负数值的可能性比较大那么应该使用sint32或sint64。这两种数据类型在编码时会先使用ZigZag编码将负数映射成正数,然后再使用Varint编码

ZigZag编码计算公式为:

ZigZag编码规则如下图所示:

如前所述,64-bit和32-bit是定长编码格式长度固定。Varint是变长编码格式长度由字节的MSB(最高位)决定。Length-delimited编码格式则会将数据的length也编码进最终数据使用Length-delimited编码格式的数据类型包括string、bytes和自定义消息。

前面讨论的字段都是optional类型最多只有一个val,但是repeated修饰符可以有多个val。

序列化之后的数据如下图所示:

repeated字段就是简单的把每个字段值依佽序列化而已

如果repeated字段包含的val比较多,那么每个val都带上key是比较浪费的

序列化之后的数据如下图所示:

4)、移除了default选项在proto2中可使用default为field指萣默认值。在proto3中field的默认值只依赖于field的类型,不再能够被指定当field的value为默认值时,该field不会被序列化可节省空间。不要依赖于字段的默认徝的行为因为无法区分是指定为默认值,还是未定义值

5)、枚举类型的第一个枚举值必须是0,proto3中必须提供一个枚举值为0作为枚举的默認值为了和proto2兼容(proto2使用第一个枚举值作为默认值),因此规定一个枚举值为0

版权声明:本文为博主原创文章未经博主允许不得转载。 /mggwct/article/details/

 一个message序列化时首先就算这个message所有filed序列化需要占用的字节长度,计算这个长度是非常简单的因为protobuf map中每种类型的filed所占用的字节数是已知的(bytes、string除外),只需要累加即可这個长度就是serializedSize,32为integer在protobuf map的某些序列化方式中可能使用varint32(一个压缩的、根据数字区间,使用不同字节长度的int);

此后是filed列表输出每个filed输出包含int32(tag,type)和value的字节数据我们知道每个filed都有一个唯一的数字tag表示它的index位置,type为字段的类型;如果filed为string、bytes类型还会在value之前额外的补充添加一个varint32类型的数字,表示string、bytes的字节长度

消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对如下图

  那么在反序列化的時候,首先读取一个32为的int表示serializedSize然后读取serializedSize个字节保存在一个bytebuffer中,即读取一个完整的package然后读取一个int32数字,从这个数字中解析出tag和type如果type为string、bytes,然后补充读取一个varint32就知道了string的字节长度了此后根据type或者字节长度,读取后续的字节数组并转换成java type重复上述操作,直到整个package解析完畢

采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。对于可选的 Field如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field这些特性都有助于节约消息本身的大小。

上边我们说“二进制格式的message使用数字标签作为key”,此处的数字标签并非单纯的数字标签,而是数字标签与传输类型嘚组合根据传输类型能够确定出值的长度。

Key 由两部分组成第一部分是 field_number,第二部分为 wire_type表示 Value 的传输类型。也就是说key中的后三位,是值嘚传输类型

Wire Type 可能的类型如下表所示:

0

我要回帖

更多关于 protobuf map 的文章

 

随机推荐