poroto3坑枚举_Proto3语⾔指南
由于⼯程项⽬中拟采⽤⼀种简便⾼效的数据交换格式,百度了⼀下发现除了采⽤ xml、JSON 还有 ProtoBuf(Google 出品),赶紧去瞄了⼀下。花了⼀个周末的时间把它⾛马观花的学习了⼀下,顺便将官⽅的指南翻译了出来。
⾸先申明,哥们⼉英语⾼中⽔平,借助了必应词典勉强将其意译了出来,如果你发现翻译中有纰漏,请⼀定不要告诉我~
Proto3 语⾔指南
定义消息类型
标准类型
默认值
枚举
使⽤其它消息类型
嵌套
更新消息类型
Any
Oneof
Map 类型
包
定义服务
JSON 映射
选项
创建您的类
本指南描述如何使⽤ ProtoBuf 语⾔规范来组织你的.proto ⽂件,以及如何编译.proto ⽂件来⽣成相应
的操作类。它涵盖了proto3 语法,如果你想查看⽼版本 proto2 的相关信息,请参考《Proto2 语⾔指南》
这是⼀个参考指南---通过⼀个例⼦⼀步⼀步地介绍本⽂档描述的 proto3 语⾔特性,请根据你选择的编程语⾔参考基础教程。
定义消息类型
让我们先来看⼀个简单的例⼦。假设你想定义⼀个搜索请求的消息格式,它包含⼀个查询字符串、⼀个你感兴趣的特定页号、以及每页结果数。下⾯就是这个.proto ⽂件所定义的消息类型。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
⽂件的第⼀⾏指定你正在使⽤ proto3 语法:如果你不这么做 protocol buffer 编译器会假设您使⽤的是 proto2 。 这⼀⾏不允许存在空⽩字符或注释。
这个 SearchRequest 消息定义了三个字段(名称/值对),每⼀条 SearchRequest 消息类型的数据都包含这三个字段定义的数据。每个字
指定字段类型
在上⾯的例⼦中,所有的字段都是 标准 类型:⼆个整形(page_number 和 resulet_per_page)和⼀个字符串类型(query)。然⽽,你也可以⽤复杂类型来定义字段,包括 枚举 和其它消息类型。
指定标签
通过上⾯的例⼦你可以看到,这⾥每个字段都定义了⼀个唯⼀的数值标签。 这些唯⼀的数值标签⽤来标识 ⼆进制消息 中你所定义的字段,⼀旦定义了编译后就⽆法修改。需要特别提醒的是标签 1–15 标识的字段编码仅占⽤ 1 个字节(包括字段类型和标识标签),更多详细的信息请参考 ProtoBuf 编码 。 数值标签 16–2047 标识的字段编码占⽤ 2 个字节。因此,你应该将标签 1–15 留给那些在你的消息类型中使⽤频率⾼的字段。记得预留⼀些空间(标签 1–15)给将来可能添加的⾼频率字段。
最⼩的数值标签是 1, 最⼤值是 2 29 - 1, 即 536,870,911。 你不能使⽤的标签范围还有:19000–19999
( FieldDescriptor::kFirstReservedNumber – FieldDescriptor::kLastReservedNumber ),这些是 ProtoBuf 系统预留的,如果你在你的.proto ⽂件中使⽤了其中的数值标签,protoc 编译器会报错。同样地,你不能使⽤保留字段中 reserved 关键字定义的标签。
定义字段的规则
消息的字段可以是⼀下情况之⼀:
单数(默认):该字段可以出现 0 或 1 次(不能⼤于 1 次)。
可重复(repeated):该字段可以出现任意次(包含 0)。 可重复字段数值的顺序是系统预定义的。
由于⼀些历史原因,默认情况下,数值类型的可重复(repeated)字段的编码性能没有想象中的好,你应该在其后⽤特殊选项
[packed=true] 来申
明以获得更⾼效的编码。 例如:
repeated int32 samples = 4 [packed=true];
你能够在 ProtoBuf 编码 中查阅更多的关于 packed 关键字的信息
添加更多的消息类型
同⼀个.proto ⽂件中可以定义多个消息类型。这在定义多个相关的消息时⾮常有⽤。例如,如果你想针对⽤于搜索查询的 SearchRequest 消息定义
⼀个保存查结果的 SearchResponse 消息,你可以把它们放在同⼀个.proto ⽂件中:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加注释
在.proto ⽂件中,使⽤ C/C++格式的注释语法 // syntax
message SearchRequest {
string query = 1;
int32 result_per_page = 3; // Number of results to return per page.
}
保留字段
如果你通过直接删除或注释⼀个字段的⽅式 更新 了⼀个消息结构,将来别⼈在更新这个消息的时候可能会重复使⽤标签。如果他们以后加载旧版
本的相同的.proto ⽂件,可能会导致严重的问题。包括数据冲突、 隐秘的 bug 等等。为了保证这种情况不会发⽣,当你想删除⼀个字段的时候,
可以使⽤ reserved 关键字来申明该字段的标签(和/或名字,这在 JSON 序列化的时候也会产⽣问题)。 将来如果有⼈使⽤了你使⽤reserved
关键字定义的标签或名字,编译器就好报错。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注意:你不能同时在⼀条 reserved 语句中申明标签和名字。
.proto ⽂件编译⽣成了什么?
当你使⽤ protoc 编译器 编译⼀个.proto ⽂件的时候,编译器会根据你选择的语⾔和你在这个.proto ⽂
件定义的消息类型⽣成代码,,这些代码的
功能包括:字段值的 getter,setter,消息序列化并写⼊到输出流,从输⼊流接反序列化读取消息等。
对于 C++语⾔,编译器会根据定义的.proto ⽂件编译⽣成⼀个.h 头⽂件和⼀个.cc 源码实现⽂件。
4
对于 Java 语⾔,编译器会为每⼀个消息类型创建⼀个带类的.java ⽂件,同时这个 java ⽂件中包含⽤来创建该消息类型的实例的特殊Builder 构造
类。
Python 语⾔有点不⼀样---编译器在你定义的.proto ⽂件中创建⼀个包含静态描述符的模块,每个消息类型对应⼀个静态描述符,在Python 程序解
释运⾏的时候,会根据静态描述符⽤⼀个元类去创建相应的数据访问类。
对于 Go 语⾔,针对每⼀个定义的消息类型编译器会创建⼀个带类型的.pb.go ⽂件。
对于 Ruby 语⾔,编译器会创建⼀个带 Ruby 模块的.rb ⽂件,其中包含了所有你定义的消息类型。
对于 JavaNano,编译器会创建 Java 语⾔类似的输出⽂件,但是没有 Builder 构造类。
对于 Ojective-C,编译器会创建⼀个 pbobjc.h 和⼀个 pbobjc.m ⽂件,为每⼀个消息类型都创建⼀个类来操作。
对于 C#语⾔,编译器会为每⼀个.proto ⽂件创建⼀个.cs ⽂件,为每⼀个消息类型都创建⼀个类来操作。
针对所选择的不同的编程语⾔,你能够在后续的教程中到更多的关于操作它们的编程接⼝(proto3 版本的即将推出)。 更详细
的针对特定编程语⾔的 API 操作细节,请参考 API 参考 。
标准类型
.proto⽂件中消息结构⾥⽤于定义字段的标准数据类型如下表所⽰,后⾯⼏列是.proto⽂件中定义的标准类型编译转换后在编程语⾔中的类
如果你想了解这些数据类型在序列化的时候如何编码,请参考 ProtoBuf 编码 。
[1] 在 Java 中,⽆符号的 32 位整形和 64 位整形都是⽤的相应的有符号整数表⽰,最⾼位储存的是符号标志。
[2] 在任何情况下,给字段赋值都会执⾏类型检查,以确保所赋的值是有效的。
5
[3] 默认情况下 64 位整数或 32 位⽆符号整数通常在编码的时候都是⽤ long 类型表⽰,但是你可以在设定字段的时候指定位 int 类型。在任何情况
下,这个值必须匹配所设定的数据类型。参考[2]
[4] Python 中字符串通常编码为 Unicode,但是如果给定的字符串是 ASCII 类型,可以设置位 str 类型(可能会有变化)
默认值
当⼀个消息被解析的时候,如果在编码后的消息结构中某字段没有初始值,相应的字段在被解析的对象中会被设置默认值。这些默认值都是类型相关的。
字符串默认值为空字符串。
字节类型默认值是空字节。
布尔类型默认值为 false。
数值类型默认值位 0。
枚举 类型默认值是第⼀个枚举元素,它必须为 0。
消息类型字段默认值为 null。
可重复类型字段的默认值为(相应编程语⾔中的)空列表。
需要提醒的是:对于标准数据类型的字段,当消息被解析的时候你是没有办法显⽰地设定默认值的(例如布尔类型是否默认设置为 false),记住当你定义⾃⼰的消息类型的时候不要设置它的默认值。例如,不要在你的消息类型中定义⼀个表⽰开关变量的布尔类型字段,如果你不希望它默认初始化为 false 的话。 还要注意的是,在序列化的时候,如果标准类型的字段的值等于它的默认值,这个值是不会存储到介质上的。
枚举
当你定义⼀个消息的时候,你可能会希望某个字段在预定的取值列表⾥⾯取值。 例如,假设你想为 SearchRequest 消息定义⼀个 corpus 字段,它的取值可能是 UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS 或者 VIDEO。你只需要简单的利⽤ enum 关键字定义⼀个枚举类型,它的每⼀个可能的取值都是常量。
在下⾯的例⼦中,我们定义了⼀个名为 Corpus 的枚举类型,并⽤它定义了⼀个字corpus。
message SearchRequest {
string query = 1;
int32 page_number = 2;import语句
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
你会发现,这个 Corpus 枚举类型的第⼀个常量被设置为 0,每个枚举类型的定义中,它的第⼀个元素都应该是⼀个等于 0 的常量。 这是因为:
只有把它的第⼀个元素设置为 0,我们才能为枚举类型定义数值类型的 默认值 。
这个为 0 的元素必须是第⼀个元素,为了兼容 proto2 语法(proto2 中枚举类型的第⼀个元素总是默认值)。
你可以通过给不同的枚举常量赋同样的值的⽅式来定义别名。 为了定义别名,你需要设置 allow_alias=true,否则编译器会报错。
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
/
/ RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
枚举常量的取值范围是 32 位整数值的范围。 由于枚举的值采⽤ varient 编码 ⽅式,负数编码效率低所以不推荐。枚举类型可以定义在消息结构体内部(上例所⽰),也可以定义在外部。如果定义在了外部,同⼀个.proto ⽂件中的所有消息都能重⽤这个枚举类型。当然,你也可以⽤
MessageType.EnumType 语法格式在⼀个消息结构内部引⽤其它消息结构体内部定义的枚举类型来定义字段。
当你⽤ protoc 编译器编译⼀个包含枚举类型的.proto ⽂件时,对于 Java 或 C++编译⽣成的代码中会包含相应的枚举类型,对于 Python 语⾔会⽣成
⼀个特殊的 EnumDescriptor 类,在 Python 运⾏时会⽣成⼀系列整形的符号常量供程序使⽤。
在消息反序列化的时候,⽆法识别的枚举类型会被保留在消息中,但是这种枚举类型如何复现依赖于所使⽤的编程语⾔。对于⽀持开放式枚举类型的编程语⾔,枚举类型取值超出特定的符号范围,例如 C++和 Go 语⾔,未知的枚举值在复现的时候简单地以基础型整数形式存储。对于⽀持封闭式枚举类
型的编程语⾔,例如 Java,未知的枚举值可以通过特殊的访问函数读取。在任何情况下,只要消息被序列化,⽆法识别的枚举值也会跟着被序列化。
欲详细了解枚举类型如何在消息类型内⼯作,请根据你选择的编程语⾔,参考 ⽣成代码参考
使⽤其它消息类型
你可以使⽤其它消息类型来定义字段。 假如你想在每⼀个 SearchResponse 消息⾥⾯定义⼀个 Result 消息类型的字段,你只需要同⼀个.proto⽂件中定义 Result 消息,并⽤它来定义 SearchResponse 中的⼀个字段即可。
message SearchResponse {
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论