8.?更新Message類型
如果現有message類型不能在滿足業務需求,例如,需要新增一個字段,但是我們卻希望依然能夠使用原來的.proto生成的代碼。完全沒有問題,僅需記住如下規則:
- 千萬不要修改現有字段后邊的數值標簽
- 只能新增optional或者repeated字段
- 可以刪除非必須字段,但是他們的數字標簽不能再被使用。最好的方法是不刪除,而是修改名字,比如在前綴上加OBSOLETE_,這樣就可以避免后人盡量少的出錯。
- 非required字段可以轉化成extension字段,反之亦然,同時保留原類型和數字標簽
- int32, uint32, int64, uint64, 和bool是兼容的。即這些字段可以相互切換,在代碼處理的時候,不會出錯,但是小心范圍小的數據接收范圍大的數據會發生截斷
- sint32, sint64是相互兼容的,但是不與其他整型類型兼容
- string和bytes是兼容的,因為bytes也是合法的UTF-8
- Embedded messages are compatible with bytes if the bytes contain an encoded version of the message(不知道怎么翻譯了)
- fixed32與 sfixed32兼容, fixed64 與sfixed64兼容
- optional與repeated兼容,也存在數據截斷,假如講一個repeated的序列化后的數據作為輸入給客戶端,客戶端會截取最后一個原子類型的字節。或者,如果是一個message類型的字段的話,合并所有的元素。
- 可以修改字段默認值
?
?
?
?
?
?
1. 概述
前兩篇文章,我們概括介紹《 Google Protocol Buffers 概述 》以及帶領大家簡單的《 Google Protocol Buffers 入門 》,接下來,再稍微詳細一點介紹Protocol Buffers書寫語言。該篇文章主要講解如何使用PB語言構建數據,包括.proto文件語法及如果使用.proto文件生成數據存取類。
本篇主要包括:
- 定義一個PB message類型
- 介紹PB 數據類型
- Optional字段及其默認值
- 枚舉類型
- 使用其他Message類型作為filed類型
- 嵌套類型
- 更新Message
2.?定義一個PB message類型
假如現在需要定義搜索請求的message格式,每條message包含三個字段:搜索語句(query string),需要的返回結果頁數(page_number),以及該頁上的結果數。可如下定義.proto文件。
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; }
該message定義聲明三個字段(name/value pairs),每個字段有一個名字和類型。
?2.1 聲明字段類型
上例中,所有的字段類型均為標準類型:兩個整型和一個字符串類型。當然,也可以指定復合類型:枚舉類型和其他自定義message類型。
2.2 給字段賦值數字標簽
從上例中可以發現,message中定義的每個字段都有一個唯一的數字標簽。該標簽的作用是在二進制message中唯一標示該字段,一旦定義該字段的值就不能夠再更改。有一點需要強調:1~15的數字標簽編碼后僅占一個字節(byte),包括數字標簽和字段類型。16~2047的數字標簽占兩個字節(byte)。因此,1~15的數字標簽應該用于最頻繁出現的元素。設計時要考慮到不要一次用完1~15的標簽,要考慮到將來也可能出現頻繁出現的元素。
最小的數字標簽是1,最大的數字標簽是2的29次方-1,也即?536,870,911。但是并不是這之間所有的數字標簽你都能用,例如19000~19999。這個區間的數字標簽就像是java中的保留字一樣,他們是PB的保留數字標簽。如果該區間的數字標簽出現在.proto文件中,PB編譯器會出錯。
2.3 字段標示符
字段標示符有三個:
message的沒一個字段,都要用如下的三個修飾符(modifier)來聲明:
- required:必須賦值,不能為空,否則該條message會被認為是“uninitialized”。build一個“uninitialized” message會拋出一個RuntimeException異常,解析一條“uninitialized” message會拋出一條IOException異常。除此之外,“required”字段跟“optional”字段并無差別。
- optional:字段可以賦值,也可以不賦值。假如沒有賦值的話,會被賦上默認值。對于簡單類型,默認值可以自己設定,例如上例的PhoneNumber中的PhoneType字段。如果沒有自行設定,會被賦上一個系統默認值,數字類型會被賦為0,String類型會被賦為空字符串,bool類型會被賦為false。對于內置的message,默認值為該message的默認實例或者原型,即其內所有字段均為設置。當獲取沒有顯式設置值的optional字段的值時,就會返回該字段的默認值。
- repeated:該字段可以重復任意次數,包括0次。重復數據的順序將會保存在protocol buffer中,將這個字段想象成一個可以自動設置size的數組就可以了。
由于一些歷史原因,數字類型的repeated字段性能有些不盡人意,但是,PB已經做了改進,但是需要再添加一點改動,即在聲明后添加[packed=true]例如:
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的。但也并不是所有的人都這么想。
2.4 同一.proto文件定義多個message
PB支持同一.proto文件定義多個message。這在需要定義相關message的時候非常有用,例如:除了搜索請求message,還需要定義搜索響應message,可以再同一.proto文件中定義:
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } message SearchResponse { ... }
2.5 添加評論
使用C/C++風格的注釋 // syntax,如下例子:
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. }
2.6 編譯.proto文件后產生了什么?
用PB 編譯器運行.proto文件后,會按照定義的格式,生成指定語言的一系列代買,這些代碼的功能包括:字段值的getter,setter,序列化message并寫入到輸出流,從輸入流接寫成message等。
對于Java,編譯器生成一個.java文件,該java文件內包含幾個內部類,分別對應.proto文件中定義的message 類型,以及將來用于創建message類實例的Builder類。
3. 標準值類型
.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 |
4.?Optional字段及其默認值
上面提到,PB允許設置可選字段(optional)。顧名思義,在一條message中,該字段可設值也可不設。假如沒有設置,那么在解析該字段的時候,會根據該字段類型,給其賦一個類型默認值。除此之外,也可以在定義message格式的時候,就為optional字段設置一個默認值,如下:
optional int32 result_per_page = 3 [default = 10];
假如沒有賦值的話,會被賦上默認值。對于簡單類型,默認值可以自己設定,例如上例的PhoneNumber中的PhoneType字段。如果沒有自行設定,會被賦上一個系統默認值,數字類型會被賦為0,String類型會被賦為空字符串,bool類型會被賦為false。對于枚舉類型,默認值是枚舉列表中第一個值。
5. 枚舉類型
在定義message類型的時候,也許會有這樣一種需求:其中的一個字段僅需要包含預定義的若干個值即可。比如,對于每一個搜索請求,現需要增加一個分類字段,分類包含:UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS or VIDEO。要實現該功能,僅需要增加一個枚舉類型字段。如下:
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編譯器會報錯。
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復用。
6.?使用其他Message類型作為filed類型
PB允許使用message類型作為filed類型。例如,在搜索相應message中,包含一個結果message。此時,只需要定義一個結果message,然后再.proto文件中,在搜索結果message中新增一個字段,該字段的類型設置為結果message即可。如下:
message SearchResponse { repeated Result result = 1; } message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; }
6.1 導入定義
在上例中,Result message類型與SearchResponse 定義在同一個文件中,假如有這么一種情況,這里所要使用的Resultmessage已經在其他的.proto文件中定義了呢?
可以通過導入其他.proto文件來使用其內的定義。為達此目的,需要在現.proto文件前增加一條import語句:
import "myproject/other_protos.proto";
7. 嵌套類型
PB支持message內嵌套message,如下例子中,Result message 定義在了SearchResponse內:
message SearchResponse { message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; } repeated Result result = 1; }
如果想要在父Message外復用該message的話,可以用Parent.Type格式來引用。
message SomeOtherMessage { optional SearchResponse.Result result = 1; }
PB支持無限深層次的message嵌套:
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; } } }
8.?更新Message類型
如果現有message類型不能在滿足業務需求,例如,需要新增一個字段,但是我們卻希望依然能夠使用原來的.proto生成的代碼。完全沒有問題,僅需記住如下規則:
- 千萬不要修改現有字段后邊的數值標簽
- 只能新增optional或者repeated字段
- 可以刪除非必須字段,但是他們的數字標簽不能再被使用。最好的方法是不刪除,而是修改名字,比如在前綴上加OBSOLETE_,這樣就可以避免后人盡量少的出錯。
- 非required字段可以轉化成extension字段,反之亦然,同時保留原類型和數字標簽
- int32, uint32, int64, uint64, 和bool是兼容的。即這些字段可以相互切換,在代碼處理的時候,不會出錯,但是小心范圍小的數據接收范圍大的數據會發生截斷
- sint32, sint64是相互兼容的,但是不與其他整型類型兼容
- string和bytes是兼容的,因為bytes也是合法的UTF-8
- Embedded messages are compatible with bytes if the bytes contain an encoded version of the message(不知道怎么翻譯了)
- fixed32與 sfixed32兼容, fixed64 與sfixed64兼容
- optional與repeated兼容,也存在數據截斷,假如講一個repeated的序列化后的數據作為輸入給客戶端,客戶端會截取最后一個原子類型的字節。或者,如果是一個message類型的字段的話,合并所有的元素。
- 可以修改字段默認值
?9. Package
PB建議在.proto文件開頭添加一個package說明符來避免不同message類型的名字沖突:
package foo.bar; message Open { ... }
這樣,就可以使用該package標示符來定義該message類型的字段:
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
推薦閱讀順序,希望給你帶來收獲~
《 Google Protocol Buffers 概述 》
《 Google Protocol Buffers 入門 》
《 Google Protocol Buffers 編碼(Encoding) 》
http://shitouer.cn/2013/04/protocol-buffers-language-guide/
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
