前兩篇文章,咱們歸納介紹《Google Protocol Buffers 概述》以及帶領你們簡單的《Google Protocol Buffers 入門》,接下來,再稍微詳細一點介紹Protocol Buffers書寫語言。該篇文章主要講解如何使用PB語言構建數據,包括.proto文件語法及若是使用.proto文件生成數據存取類。java
本篇主要包括:數組
假如如今須要定義搜索請求的message格式,每條message包含三個字段:搜索語句(query string),須要的返回結果頁數(page_number),以及該頁上的結果數。可以下定義.proto文件。ide
1
2
3
4
5
|
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
|
該message定義聲明三個字段(name/value pairs),每一個字段有一個名字和類型。性能
上例中,全部的字段類型均爲標準類型:兩個整型和一個字符串類型。固然,也能夠指定複合類型:枚舉類型和其餘自定義message類型。ui
從上例中能夠發現,message中定義的每一個字段都有一個惟一的數字標籤。該標籤的做用是在二進制message中惟一標示該字段,一旦定義該字 段的值就不可以再更改。有一點須要強調:1~15的數字標籤編碼後僅佔一個字節(byte),包括數字標籤和字段類型。16~2047的數字標籤佔兩個字 節(byte)。所以,1~15的數字標籤應該用於最頻繁出現的元素。設計時要考慮到不要一次用完1~15的標籤,要考慮到未來也可能出現頻繁出現的元 素。this
最小的數字標籤是1,最大的數字標籤是2的29次方-1,也即 536,870,911。可是並非這之間全部的數字標籤你都能用,例如 19000~19999。這個區間的數字標籤就像是java中的保留字同樣,他們是PB的保留數字標籤。若是該區間的數字標籤出如今.proto文件 中,PB編譯器會出錯。google
字段標示符有三個:編碼
message的沒一個字段,都要用以下的三個修飾符(modifier)來聲明:url
因爲一些歷史緣由,數字類型的repeated字段性能有些不盡人意,可是,PB已經作了改進,可是須要再添加一點改動,即在聲明後添加[packed=true]例如:spa
1
|
repeated int32 samples =
4
[packed=
true
];
|
Notice:應該格外當心定義Required字段。當由於某緣由要把Required字段改成 Optional字段是,會有問題,老版本讀取器會認爲消息中沒有該字段不完整,可能會拒絕或者丟棄該字段(Google文檔是這麼說的,可是我試了一 下,將required的改成optional的,再用原來required時候的解析代碼去讀,若是字段賦值的話,並不會出錯,可是若是字段未賦值,會 報這樣錯誤:Exception in thread 「main」 com.google.protobuf.InvalidProtocolBufferException: Message missing required fields:fieldname)。在設計時,儘可能將這種驗證放在應用程序端的完成。Google的一些工程師對此也很困惑,他們覺 得,required類型壞處大於好處,應該儘可能僅適用optional或者repeated的。但也並非全部的人都這麼想。
PB支持同一.proto文件定義多個message。這在須要定義相關message的時候很是有用,例如:除了搜索請求message,還須要定義搜索響應message,能夠再同一.proto文件中定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
message SearchRequest {
required string query =
1
;
optional int32 page_number =
2
;
optional int32 result_per_page =
3
;
}
message SearchResponse {
...
}
|
使用C/C++風格的註釋 // syntax,以下例子:
1
2
3
4
5
6
7
8
9
|
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;// Which page number do we want?
optional int32 result_per_page = 3;// Number of results to return per page.
}
|
用PB 編譯器運行.proto文件後,會按照定義的格式,生成指定語言的一系列代買,這些代碼的功能包括:字段值的getter,setter,序列化message並寫入到輸出流,從輸入流接寫成message等。
對於Java,編譯器生成一個.java文件,該java文件內包含幾個內部類,分別對應.proto文件中定義的message 類型,以及未來用於建立message類實例的Builder類。
.proto Type | Notes | C++ Type | Java Type | Python Type[2] |
---|---|---|---|---|
double | double | double | float | |
float | float | float | float | |
int32 | 使用可變長編碼. 對於負數比較低效,若是負數較多,請使用sint32 | int32 | int | int |
int64 | 使用可變長編碼. 對於負數比較低效,若是負數較多,請使用sint64 | int64 | long | int/long |
uint32 | 使用可變長編碼 | uint32 | int |
int/long |
uint64 | 使用可變長編碼 | uint64 | long |
int/long |
sint32 | 使用可變長編碼. Signed int value. 編碼負數比int32更高效 | int32 | int | int |
sint64 | 使用可變長編碼. Signed int value. 編碼負數比int64更高效 | int64 | long | int/long |
fixed32 | 恆定四個字節。若是數值幾乎老是大於2的28次方,該類型比unit32更高效。 | uint32 | int |
int |
fixed64 | 恆定四個字節。若是數值幾乎老是大於2的56次方,該類型比unit64更高效。 | uint64 | long |
int/long |
sfixed32 | 恆定四個字節 | int32 | int | int |
sfixed64 | 恆定八個字節 | int64 | long | int/long |
bool | bool | boolean | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode |
bytes | 包含任意數量順序的字節 | string | ByteString | str |
上面提到,PB容許設置可選字段(optional)。顧名思義,在一條message中,該字段可設值也可不設。假如沒有設置,那麼在解析該字段的時候,會根據該字段類型,給其賦一個類型默認值。除此以外,也能夠在定義message格式的時候,就爲optional字段設置一個默認值,以下:
1
|
optional int32 result_per_page = 3 [default = 10];
|
假如沒有賦值的話,會被賦上默認值。對於簡單類型,默認值能夠本身設定,例如上例的PhoneNumber中的PhoneType字段。若是沒有自行設定,會被賦上一個系統默認值,數字類型會被賦爲0,String類型會被賦爲空字符串,bool類型會被賦爲false。對於枚舉類型,默認值是枚舉列表中第一個值。
在定義message類型的時候,也許會有這樣一種需求:其中的一個字段僅須要包含預約義的若干個值便可。好比,對於每個搜索請求,現須要增長一個分類字段,分類包含:UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS or VIDEO。要實現該功能,僅須要增長一個枚舉類型字段。以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
optional Corpus corpus = 4 [default = UNIVERSAL];
}
|
還能夠給枚舉值設置別名,僅需將相同的數字標籤設置給不一樣的名稱便可。這裏,必須得設置allow_alias爲true,不然PB編譯器會報錯。
1
2
3
4
5
6
7
8
9
10
11
12
|
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.
}
|
能夠定義枚舉在一個message內部,如上例。也能夠定義在message的外部,這樣的枚舉能夠被其餘任何.proto文件內的message複用。
PB容許使用message類型做爲filed類型。例如,在搜索相應message中,包含一個結果message。此時,只須要定義一個結果 message,而後再.proto文件中,在搜索結果message中新增一個字段,該字段的類型設置爲結果message便可。以下:
1
2
3
4
5
6
7
8
9
|
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
|
在上例中,Result message類型與SearchResponse 定義在同一個文件中,假若有這麼一種狀況,這裏所要使用的Resultmessage已經在其餘的.proto文件中定義了呢?
能夠經過導入其餘.proto文件來使用其內的定義。爲達此目的,須要在現.proto文件前增長一條import語句:
1
|
import "myproject/other_protos.proto";
|
PB支持message內嵌套message,以下例子中,Result message 定義在了SearchResponse內:
1
2
3
4
5
6
7
8
|
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
|
若是想要在父Message外複用該message的話,能夠用Parent.Type格式來引用。
1
2
3
|
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}
|
PB支持無限深層次的message嵌套:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
required int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
required int32 ival = 1;
optional bool booly = 2;
}
}
}
|
若是現有message類型不能在知足業務需求,例如,須要新增一個字段,可是咱們卻但願依然可以使用原來的.proto生成的代碼。徹底沒有問題,僅需記住以下規則:
PB建議在.proto文件開頭添加一個package說明符來避免不一樣message類型的名字衝突:
1
2
3
|
package foo.bar;
message Open { ... }
|
這樣,就可使用該package標示符來定義該message類型的字段:
1
2
3
4
5
6
7
8
9
|
message Foo {
...
required foo.bar.Open open = 1;
...
}
|
不一樣語言,由於添加package標示符,生成的代碼也會有所不一樣,Java中,該package將會被用做java文件的package。若是不想這樣的話,也可在.proto文件中顯式指明package,該字段是:java_package。
譯自:https://developers.google.com/protocol-buffers/docs/proto
說實話,翻譯下來整個文章很是辛苦,並且都要敲代碼去親自試驗可否經過,因此若是您想轉載,很是歡迎,但請註明出處,也算是對俺辛苦的尊重~