Google Protobuf 优势:api
Protobuf 是一个灵活、高效、结构化的数据序列化框架, 相比与 xml 等传统的序列化工具, 它更小、更快、更简单.数组
Protobuf 支持数据结构化一次能够处处使用, 甚至跨语言使用, 经过代码生成工具能够自动生成不一样语言版本的源代码, 甚至能够在使用不一样版本的数据结构进程间进行数据传递, 实现数据结构前向兼容.数据结构
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
该文件的第一行指定使用 proto3
语法, 若是不写的话表示 proto2
.框架
string query = 1;
1 就是字段编号, 字段号主要用来标识二进制格式字段的. 1 到 15 字段号占一个字节. 16 到 2047 字段号须要两个字节.工具
咱们将对象转换为报文的时候, 是按照字段编号进行报文封装的; 咱们接收到数据以后框架会帮咱们按照字段号进行赋值.性能
不能使用数字19000到19999, 由于它们是为 Google Protobuf 保留的.ui
.proto Type | Notes | C++ Type | Java Type |
---|---|---|---|
double | double | double | |
float | float | float | |
int32 | 使用可变长度编码, 对负数编码效率低下 若是您的字段可能有负值, 则使用sint32代替. |
int32 | int |
int64 | 使用可变长度编码, 对负数编码效率低下 若是您的字段可能有负值, 则使用sint64代替. |
int64 | long |
uint32 | 使用可变长度编码 | uint32 | int |
uint64 | 使用可变长度编码 | uint64 long | |
sint32 | 使用可变长度编码 有符号的int值这些编码比常规int32更有效地编码负数 |
uint32 | int |
sint64 | 使用可变长度编码 有符号的int值这些编码比常规int64更有效地编码负数 |
int64 | long |
fixed32 | 四个字节, 若是值一般大于2的28次方, 则比uint32更有效 | uint32 | int |
fixed64 | 四个字节, 若是值一般大于2的56次方, 则比uint64更有效 | uint64 | long |
sfixed32 | 四个字节 | int32 | int |
sfixed64 | 四个字节 | int64 | long |
bool | bool | boolean | |
string | 字符串必须始终包含UTF-8编码或7位ASCII文本 | string | String |
bytes | 字符串必须始终包含UTF-8编码或7位ASCII文本 | string | ByteString |
还请注意, 若是消息字段设置为默认值, 则该值将不会序列化.
Protocol Buffers 定义 message 容许嵌套组合成更加复杂的消息google
message SearchResponse { repeated Result results = 1; } message Result { string url = 1; string title = 2; repeated string snippets = 3; }
更多的例子:编码
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; } message SomeOtherMessage { SearchResponse.Result result = 1; }
message Outer { // Level 0 message MiddleAA { // Level 1 message Inner { // Level 2 int64 ival = 1; bool booly = 2; } } message MiddleBB { // Level 1 message Inner { // Level 2 int32 ival = 1; bool booly = 2; } } }
能够在文件的顶部添加一个import
语句:url
import "myproject/other_protos.proto";
未知字段就是解析器没法识别的字段. 例如, 当服务端使用新消息发送数据, 客户端使用旧消息解析数据, 那么这些新字段将成为旧消息中的未知字段.
在3.5和更高版本中, 未知字段在解析过程当中被保留, 并包含在序列化中输出.
repeated
类型能够用来表示数组, Map
类型则能够用来表示字典.
map<key_type, value_type> map_field = N; map<string, Project> projects = 3;
key_type
能够是任何 int
或者 string
类型(任何的标量类型, 具体能够见上面标量类型对应表格, 可是要除去 float
、double
和 bytes
)
枚举值也不能做为 key.
key_type
能够是除去 map
之外的任何类型.
须要特别注意的是:
repeated
修饰的..proto
生成文本格式时, map 按 key 排序. 数字的 key 按数字排序.Protocol Buffer 虽然不支持 map 类型的数组, 可是能够转换一下, 用如下思路实现 maps 数组:
message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N;
上述写法和 map 数组是彻底等价的,因此用 repeated 巧妙的实现了 maps 数组的需求.
message 采用驼峰命名法. message 首字母大写开头. 字段名采用下划线分隔法命名.
message SongServerRequest { required string song_name = 1; }
枚举类型采用驼峰命名法. 枚举类型首字母大写开头. 每一个枚举值所有大写, 而且采用下划线分隔法命名.
enum Foo { FIRST_VALUE = 0; SECOND_VALUE = 1; }
每一个枚举值用分号结束, 不是逗号.
服务名和方法名都采用驼峰命名法. 而且首字母都大写开头.
service FooService { rpc GetSomething(FooRequest) returns (FooResponse); }
getDefaultInstance()
: 返回单例实例, 它与 newBuilder().build()
实例相同getDescriptor()
: 返回类型的描述符. 包括具备哪些字段以及类型. 这能够与 Message
的反射方法一块儿使用, 例如getField()
.parseFrom(...)
: 返回反序列化后的 Message
. 注意不会抛出 UninitializedMessageException
和 InvalidProtocolBufferException
异常.Message.Builder
: 中的 mergeFrom()
放会将数据解析为此类型的消息, 并进行消息合并.newBuilder()
: 建立一个新的构建器.
Any类型容许包装任意的message类型:
import "google/protobuf/any.proto"; message Response { google.protobuf.Any data = 1; }
message SubscribeReq { int32 subReqID = 1; string userName = 2; string productName = 3; string address = 4; }
能够经过 pack()
和 unpack()
(方法名在不一样的语言中可能不一样)方法装箱/拆箱,如下是Java的例子:
People people = People.newBuilder().setName("proto").setAge(1).build(); // protoc编译后生成的message类 Response r = Response.newBuilder().setData(Any.pack(people)).build(); // 使用Response包装people System.out.println(r.getData().getTypeUrl()); // type.googleapis.com/example.protobuf.people.People System.out.println(r.getData().unpack(People.class).getName()); // proto
若是你有一些字段同时最多只有一个能被设置, 可使用 oneof
关键字来实现, 任何一个字段被设置, 其它字段会自动被清空(被设为默认值):
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }
好比咱们建立了上面的消息类型, 咱们在代码中设置 builder.setSubReqID(0);
为 0, 零是数值类型的默认值; 因此咱们会看到序列化后的数据中, 没有对此字段进行序列化.
byte[] arry = builder.build().toByteArray();
arry
长度为 0. 对于字段类型是 string
类型的也是同样的; 也就是说显示赋值默认值也不会对其进行序列化.
message SubscribeReq { reserved 2; int32 subReqID = 1; string userName = 2; string productName = 3; string address = 4; }
顾名思义, 就是此字段会被保留可能在之后会使用此字段. 使用关键字 reserved
表示我要保留字段数 2.
上面代码咱们在生成 Java 文件的时候会出现 ubscribeReqPeoro.proto: Field "userName" uses reserved number 2
错误信息, 因此咱们须要将 string userName = 2;
注释, 或者删除.
保留后咱们没法对其设置或序列化和反序列化.