这篇文章翻译自https://developers.google.com/protocol-buffers/
Oneof
如果一个Message拥有一堆字段,但同时只有一个字段会被赋值,可以通过Oneof功能来节省内存。
Oneof字段会将多个常规字段共享一块内存,同时只有一个字段会被赋值。赋值任意字段会清除其他已经赋值的字段。你可以通过case()或者WhichOneof()方法检查哪个字段被赋值,根据语言不通检查方法也不同。
使用 Oneof
在proto文件中通过Oneof关键字定义一个oneof类型,下面是test_oneof例子:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
可以添加oneof字段,可以添加任意类型字段,但是不能使用repeated 。
Oneof 特征
- 设置任意值会清除其他字段赋值的值。所以如果你赋值了多个字段,只有最后一个赋值的字段会有值。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
- oneof 不能使用repeated
- 反射api对oneof类型好用。
- 如果使用c++,请确保不要导致内存崩溃。下面的例子演示了内存崩溃,因为sub_message 已经被set_name()清除掉了。
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
- 在C++中,swap()方法会交换内存。下面的例子中msg2将拥有name而msg1将拥有sub_message
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后兼容性问题
添加或者删除oneof中的字段要小心。如果检查oneof返回None/NOT_SET,它可能表示oneof没有被赋值,也可能表示赋值给了新增的字段。这里无法区分是哪种情况发生。
标签值重用问题
- 移出或者移入一个字段到oneof,Message在序列化和反序列化之后可能会丢失信息。
- 删除并重新添加字段到oneof,Message在序列化和反序列化之后可能会清除字段信息。
- 拆分或者合并oneof,与上面两个问题类似。
Maps
如果你想创建一个Map在你的数据格式定义中,protocol提供一个简单的语法:
map map_field = N;
key_type
可以是数值类型或者字符串类型。enum不能作为key_type类型。value_type可以是除了map类型其他任意类型。
例如,定义一个Projects的Map,每个Project 对应一个string类型的键。你可以想下面那样定义:
map projects = 3;
- Map字段不能是 repeated.
- Map中值的排序是不一定的。
向后兼容性
map格式化后与下面是一致的,所以即使protocol buffer实现不支持map,一样可以获取数据。
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Packages
你可以添加一个可选的package,预防不同的proto文件中Message名称冲突。
package foo.bar;
message Open { ... }
你可以使用package指定Message字段类型
message Foo {
...
foo.bar.Open open = 1;
...
}
不同语言对于package的不同处理:
- 在c++中,生成的class会被放到namespace中,上面的例子,Open会在namespace foo::bar中。
- 在java中,会按照java的package生成,除非你指定java_package在proto文件开头。
- 在Python中,package会被忽略,因为python加载是根据系统文件目录来的。
- 在go语言中,会按照go的package生成,除非你指定go_package在proto文件开头。
定义 Services
如果你想用你定义的Message完成一个RPC (Remote Procedure Call)访问,你可以定义一个RPC服务在proto文件中,protocol buffer编译器会根据你选择的语言生成对应的接口。例如,你想定义一个RPC方法,参数是SearchRequest,返回值是SearchResponse,proto文件中应如下定义:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
使用protocol buffer的RPC系统,最直接的就是gRPC:Google开发的一个多语言,跨平台,开源的RPC系统。gRPC可以和protocol buffer很好的工作,你可以通过proto文件直接生成对应的RPC代码。
这里也有一些第三方实现的RPC使用Protocol Buffer,查看 third-party add-ons wiki page。