Protobuf 编码(3)
可选和重复元素
如果proto2消息定义有重复的元素(除了[packed=true]选项),则编码的消息具有零个或多个具有相同字段编号的键值对。这些重复值不必连续出现;它们可以与其他字段交错。在解析时,元素相对于彼此的顺序会保留下来,尽管相对于其他字段的顺序会丢失。在proto3中,重复字段使用打包编码,您可以在下面阅读。
对于proto3中的任何非重复字段或proto2中的可选字段,编码消息可能具有也可能不具有与该字段编号的键值对。
通常,一个编码的消息绝不会有一个以上的不重复字段实例。然而,解析器会处理这种情况。对于数字类型和字符串,如果同一个字段出现多次,解析器将接受它看到的最后一个值。对于嵌入的消息字段,解析器合并同一个字段的多个实例,就像使用Message::MergeFrom方法一样:也就是说,后一个实例中的所有单个标量字段替换前一个实例中的字段,单个嵌入的消息被合并,重复的字段被连接。这些规则的效果是,解析两个编码消息的连接会产生完全相同的结果,就像您分别解析了这两个消息并合并了结果对象一样:
MyMessage message; message.ParseFromString(str1 + str2);
相当于:
MyMessage message, message2; message.ParseFromString(str1); message2.ParseFromString(str2); message.MergeFrom(message2);
这个属性有时很有用,因为它允许您合并两个消息,即使您不知道它们的类型。
打包重复字段
2.1.0版引入了打包的重复字段,在proto2中,这些字段被声明为类似于重复字段,但带有特殊的[packed=true]选项。在proto3中,默认情况下,标量数值类型的重复字段被打包。这些功能像重复的字段,但编码不同。编码消息中不会出现包含零个元素的打包重复字段。否则,字段的所有元素都被打包成一个具有类型2 (长度分隔)的键值对。每个元素都以正常方式编码,除了前面没有键。
例如,假设您有消息类型:
message Test4 { repeated int32 d = 4 [packed=true]; }
现在假设您构建了一个Test4,为重复的字段d提供值3、270和86942。然后,编码的形式将是:
22 // key (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)
只有原始数字类型(使用变量、32位或64位类型的类型)的重复字段才能声明为"packed"。
请注意,虽然通常没有理由为一个打包的重复字段编码多个键值对,但编码器必须准备好接受多个键值对。在这种情况下,有效载荷应该连接在一起。每对必须包含整数个元素。
Protocol Buffer解析器必须能够解析像未打包一样打包的重复字段,反之亦然。这允许以向前和向后兼容的方式将[打包=真]添加到现有字段中。
字段排序
字段编号可以在.proto 文件中以任何顺序使用。选择的顺序对消息的序列化方式没有影响。
当消息被序列化时,对于如何写入其已知或未知字段没有保证顺序。序列化顺序是一个实现细节,任何特定实现的细节都可能在将来发生变化。因此,Protocol Buffer解析器必须能够以任何顺序解析字段。
含义
不要假设序列化消息的字节输出是稳定的。尤其是对于具有表示其他序列化Protocol Buffer消息的可传递字节字段的消息。
默认情况下,对同一Protocol Buffer消息实例重复调用序列化方法可能不会返回相同的字节输出;即默认串行化不是确定性的。
--确定性序列化只保证特定二进制文件的相同字节输出。字节输出可能在二进制文件的不同版本之间发生变化。
对于Protocol Buffer消息实例foo,以下检查可能会失败。
foo.SerializeAsString() == foo.SerializeAsString() Hash(foo.SerializeAsString()) == Hash(foo.SerializeAsString()) CRC(foo.SerializeAsString()) == CRC(foo.SerializeAsString()) FingerPrint(foo.SerializeAsString()) == FingerPrint(foo.SerializeAsString())
这里有几个示例场景,其中逻辑等价的协议缓冲消息foo和bar可以序列化为不同的字节输出。
-bar由旧服务器序列化,旧服务器将某些字段视为未知字段。
-bar由用不同编程语言实现的服务器序列化,并以不同的顺序序列化字段。
-bar有一个以非确定性方式序列化的字段。
-bar有一个存储Protocol Buffer消息序列化字节输出的字段,该消息以不同方式序列化。
-bar由新的服务器序列化,由于实现的变化,该服务器以不同的顺序序列化字段。
-foo和bar都是单个消息的串联,但顺序不同。