本文是對Protobuf3(以下簡稱pb)官方文檔的學(xué)習(xí)筆記浙芙,大部分示例摘自官方。
原文:https://developers.google.com/protocol-buffers/docs/proto3
一個簡單的例子
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
版本號
對于一個pb文件而言卢厂,文件首個非空、非注釋的行必須注明pb的版本惠啄,即syntax = "proto3";
慎恒,否則默認(rèn)版本是proto2。
Message
一個message類型看上去很像一個Java class撵渡,由多個字段組成融柬。每一個字段都由類型、名稱組成趋距,位于等號右邊的值不是字段默認(rèn)值粒氧,而是數(shù)字標(biāo)簽,可以理解為字段身份的標(biāo)識符节腐,類似于數(shù)據(jù)庫中的主鍵外盯,不可重復(fù),標(biāo)識符用于在編譯后的二進(jìn)制消息格式中對字段進(jìn)行識別翼雀,一旦你的pb消息投入使用饱苟,字段的標(biāo)識就不應(yīng)該再改變。數(shù)字標(biāo)簽的范圍是[1, 536870911]狼渊,其中19000~19999是保留數(shù)字箱熬。
類型
每個字段的類型(int32
,string
)都是scalar的類型,和其他語言類型的對比如下:
修飾符
如果一個字段被repeated
修飾,則表示它是一個列表類型的字段城须,如下所示:
...
message SearchRequest {
repeated string args = 1 // 等價于java中的List<string> args
}
如果你希望可以預(yù)留一些數(shù)字標(biāo)簽或者字段可以使用reserved修飾符:
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
string foo = 3 // 編譯報錯蚤认,因為‘foo’已經(jīng)被標(biāo)為保留字段
}
默認(rèn)值
- string類型的默認(rèn)值是空字符串
- bytes類型的默認(rèn)值是空字節(jié)
- bool類型的默認(rèn)值是false
- 數(shù)字類型的默認(rèn)值是0
- enum類型的默認(rèn)值是第一個定義的枚舉值
- message類型(對象,如上文的SearchRequest就是message類型)的默認(rèn)值與 語言 相關(guān)
- repeated修飾的字段默認(rèn)值是空列表
如果一個字段的值等于默認(rèn)值(如bool類型的字段設(shè)為false)酿傍,那么它將不會被序列化烙懦,這樣的設(shè)計是為了節(jié)省流量。
枚舉
每個枚舉值有對應(yīng)的數(shù)值赤炒,數(shù)值不一定是連續(xù)的氯析。第一個枚舉值的數(shù)值必須是0且至少有一個枚舉值,否則編譯報錯莺褒。編譯后編譯器會為你生成對應(yīng)語言的枚舉類掩缓。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
一個數(shù)值可以對應(yīng)多個枚舉值,必須標(biāo)明option allow_alias = true;
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
可以使用MessageType.EnumType
的形式引用定義在其它message類型中的枚舉遵岩。
由于編碼原因你辣,出于效率考慮,官方不推薦使用負(fù)數(shù)作為枚舉值的數(shù)值尘执。
使用其它的message類型
除了上述基本類型舍哄,一個字段的類型也可以是其它的message類型:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
從上面的例子可以看到,一個.proto
文件中可以定義多個message誊锭。我們也可以引用定義在其它文件中的message:
import "myproject/other_protos.proto"; // 這樣就可以引用在other_protos.proto文件中定義的message
不能導(dǎo)入不使用的
.proto
文件表悬。
import
還有一種特殊的語法,先看下面的例子:
// new.proto
// 原來在old.proto文件中的定義移到這里
// old.proto
import public "new.proto"; // 把引用傳遞給上層使用方
import "other.proto"; // 引用old.proto本身使用的定義
// client.proto
import "old.proto";
// 此處可以引用old.proto和new.proto中的定義丧靡,但不能使用other.proto中的定義
從這個例子中可以看到import
關(guān)鍵字導(dǎo)入的定義僅在當(dāng)前文件有效蟆沫,不能被上層使用方引用(client.proto
無法使用other.proto
中的定義),而import public
關(guān)鍵字導(dǎo)入的定義可以被上層使用方引用(client.proto
可以使用new.proto
中的定義)温治,import public
的功能可以看作是import
的超集饭庞,在import
的功能上還具有傳遞引用的作用。
嵌套類型
你可以在一個message類型中定義另一個message類型熬荆,并且可以一直嵌套下去舟山,類似Java的內(nèi)部類:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
可以使用Parent.Type
的形式引用嵌套的message:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
Any
Any類型允許包裝任意的message類型:
import "google/protobuf/any.proto";
message Response {
google.protobuf.Any data = 1;
}
可以通過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
Any對包裝的類型會生成一個URL惶看,默認(rèn)是type.googleapis.com/packagename.messagename
(在Java中可以通過這個特性進(jìn)行反射操作)捏顺。
Oneof
如果你有一些字段同時最多只有一個能被設(shè)置,可以使用oneof
關(guān)鍵字來實現(xiàn)纬黎,任何一個字段被設(shè)置幅骄,其它字段會自動被清空(被設(shè)為默認(rèn)值):
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
oneof
塊中的字段不支持repeated
。
Maps
pb中也可以使用map類型(官方并不認(rèn)為是一種類型本今,此處稱之為類型僅便于理解)拆座,絕大多數(shù)scalar類型都可以作為key主巍,除了浮點型和bytes,枚舉型也不能作為key挪凑,value可以是除了map以外的任意類型:
// map<key_type, value_type> map_field = N;
map<string, Project> projects = 3;
map
類型字段不支持repeated
孕索,value的順序是不定的。
map其實是一種語法糖躏碳,它等價于以下形式:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
包
你可以用指定package
以避免類型命名沖突:
package foo.bar;
message Open { ... }
然后可以用類型的全限定名來引用它:
message Foo {
...
foo.bar.Open open = 1;
...
}
指定包名后搞旭,會對生成的代碼產(chǎn)生影響,以Java為例菇绵,生成的類會以你指定的package
作為包名肄渗。
JSON映射
pb支持和JSON互相轉(zhuǎn)換。如果一個字段不存在JSON數(shù)據(jù)中或者為null
咬最,那么pb中會被賦為該字段的默認(rèn)值翎嫡,反之,如果一個字段在pb中是默認(rèn)值永乌,那么不會寫到JSON數(shù)據(jù)中以節(jié)省空間惑申。
選項
選項不對message的定義產(chǎn)生任何的效果,只會在一些特定的場景中起到作用翅雏,下面是一部分例子圈驼,完整的選項列表可以前往google/protobuf/descriptor.proto
查看(Java語言可以在jar包中找到):
-
option java_package = "com.example.foo";
編譯器為以此作為生成的Java類的包名,如果沒有該選項望几,則會以pb的package
作為包名碗脊。 -
option java_multiple_files = true;
該選項為true
時,生成的Java類將是包級別的橄妆,否則會在一個包裝類中。 -
option optimize_for = CODE_SIZE;
該選項會對生成的類產(chǎn)生影響祈坠,作用是根據(jù)指定的選項對代碼進(jìn)行不同方面的優(yōu)化害碾。 -
int32 old_field = 6 [deprecated=true];
把字段標(biāo)為過時的。
Java例子
最后赦拘,用Java寫了一個簡單的例子:Github
謝謝慌随。