Protocol Buffers(Protobuf) 官方文件--Protobuf語言指南
約定:為方便書寫,ProtocolBuffers在下文中將已Protobuf代替。
本指南將向您描述如何使用protobuf定義i結構化Protobuf資料,包括.proto檔案語法和如何使用.proto檔案生成資料存取類。
作為一個參考指南,本文件將以示例的形式一步步向您介紹Protobuf的特點。您可以參考您所選擇的語言的示例。tutorial
--------------------------------------小小的分割線-----------------------------------------
定義一個訊息型別
首先,看一個非常簡單的例子,比如說你想定義一個 搜尋請求訊息 ,每個搜尋請求都有一個 查詢的字串(關鍵字:比如我們上百度搜索 《報告老闆》),和我們搜尋出來的一個感興趣的網頁,以及搜尋到的所有網頁總數。 來看看這個.proto檔案是如何定義的。
1 message SearchRequest { 2 required string query = 1; 3 optional int32 page_number = 2; 4 optional int32 result_per_page = 3; 5 }
這個"搜尋請求"訊息指定了三個欄位(名稱/屬性 組合),每一個你想要包含在這型別的資訊內的東西,都必須有一個欄位,每個欄位有一個名稱和型別!
指定欄位型別
在上面的示例中,所有的欄位都是標量型別(scalar types):兩個整數(integers:page_number
和 result_per_page)和一個字串(string:query:查詢的關鍵字),不過你可以在你的欄位內指定符合型別。包括列舉型別(
和其他的訊息型別
分配指定標籤號
如你所見,每個訊息的欄位都有一個唯一的數字標籤,這些標籤用來表示你的欄位在二進位制訊息(message binary format)中處的位置。並且一旦指定標籤號,在使用過程中是不可以更改的,標記這些標籤號在1-15的範圍內每個欄位需要使用1個位元組用來編碼這一個位元組包括欄位所在的位置和欄位的型別!(需要更多關於編碼的資訊請點選Protocol Buffer Encoding)。標籤號在16-2047需要使用2個位元組來編碼。所以你最好將1-15的標籤號為頻繁使用到的欄位所保留。如果將來可能會新增一些頻繁使用到的元素,記得留下一些1-15標籤號。
最小可指定的標籤號為1,最大的標籤號為229 - 1或者536870911。不能使用19000-19999的標籤號(FieldDescriptor::kFirstReservedNumber 至 FieldDescriptor::kLastReservedNumber) 這些標籤號是為protobuf內部實現所保留的,如果你在.proto檔案內使用了這些標籤號Protobuf編譯器將會報錯!
指定欄位規則
訊息欄位可以被指定為以下三種:
required
: 完整的訊息內必須擁有此欄位。此欄位是必須擁有的 (雙方都要有)optional
: 完整的訊息內此欄位是可選的,可擁有也可以沒有(雙方可選)repeated
: 完整的訊息內本欄位的值可以擁有任意個,重複的值的次數會儲存下來。(雙方可選,陣列)
因為歷史的原因:repeated欄位如果是基本的數字型別的話會無法編碼。新的程式碼應該使用特殊的關鍵字[packed=true] 來使其得到有效的編碼.例如
repeated int32 samples = 4 [packed=true];
注意:你應該小心將欄位設定為required,如果你希望在某些情況下取消required欄位的讀寫,它將改變欄位為optional屬性,舊的的讀取方將會認為此訊息不完全。可能會無意的將其丟棄。你應該考慮自定義一個訊息檢查程式。google的一些工程師認為使用optinal欄位的好處大於required。但是顯然這個觀點並不是通用的。
新增更多的訊息型別
多個訊息型別可以定義在同一個.proto檔案內,這對定義多個有關聯的訊息是是十分有用的。例如,如果你想定義一個用於回覆SearchResponse訊息,你可以像這樣在.proto內新增。
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
...
}
添加註釋
添加註釋的方式和C/C++是一樣的。使用//
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.
}
.proto檔案會生成什麼?
當你使用protobuf編譯器編譯一個.proto檔案,它會生成在.proto內你描述的訊息型別的操作程式碼,這些程式碼是根據你所選擇的程式設計功能語言決定的。這些操作程式碼內包含了設定欄位值 和讀取欄位值,以及序列化到輸出流 和 從輸入流反序列化。
C++:編譯器會按照每個.proto檔案生成與其對應的.h和.cc檔案,每個訊息類似都有獨立的訊息操作類。
Java:編譯器將會生成一個.java檔案和一個操作類,此操作類為所有訊息型別所共有, 使用一個特別的Builder類為每個訊息型別例項化
.
Python:有一點不同 – 編譯器會為每個訊息生成一個模組每個模組有一個靜態描述符, 該模組與一個元類在執行時建立一個需要的資料操作類。
從你所選擇語言的例程,你可以找到更多關於API的內容, 需要關於API的詳細資訊, 參考: API reference.
標量值型別
一個訊息的欄位如果要使用標量可使之為以下型別 –這個表格顯示了在.proto檔案內可以指定的型別, 與自動生成的相對型別!
.proto Type | Notes | C++ Type | Java Type | Python Type[2] |
---|---|---|---|---|
double | double | double | float | |
float | float | float | float | |
int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int |
int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long[3] |
uint32 | Uses variable-length encoding. | uint32 | int[1] | int/long[3] |
uint64 | Uses variable-length encoding. | uint64 | long[1] | int/long[3] |
sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int |
sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long[3] |
fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 228. | uint32 | int[1] | int |
fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 256. | uint64 | long[1] | int/long[3] |
sfixed32 | Always four bytes. | int32 | int | int |
sfixed64 | Always eight bytes. | int64 | long | int/long[3] |
bool | bool | boolean | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode[4] |
bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str |
你可以在Protocol Buffer Encoding.找到更多關於.這些型別如何編碼,如何序列化定義訊息的資訊!
[1] 在Java中, 無符號32位和64位整數與其有符號相對應, 最高位用來儲存符號!
[2] 在所有情況下, 設定某個欄位的值將會執行型別檢查確保其值是合法的!
[3] 64位或32位無符號整數在解碼中會以long來解碼, 給欄位賦值的時候可以是int.但是在所有情況下,賦值的時候會轉變為其目標型別 . 詳見 [2].
[4] Python的字串在解碼時候會以unicode來描述,但是同樣的可以給其賦值為ascii字串 (此乃弦外之音).
Optional 欄位與其預設值
如上所述,在描述一個訊息的時候可以用optional指定欄位約束,一個訊息可以包含也可以不包含optional元素。當一個訊息被解析,如果其沒有一個optional欄位,被解析的訊息物件就會將其相對的欄位設定為其欄位的預設值。這個預設值可以在描述訊息的時候被指定。例如。比如你想設定SearchRequest的
result_per_page的預設值為10.
optional int32 result_per_page = 3 [default = 10];
如果一個optional欄位沒有被指定其預設值。其預設值被自動替換為:
1.字串:為空字串.
2.bool:為false.
3.數字型別:為0;
4.列舉值:為第一個列舉值
列舉值
當你定義訊息格式的時候, 也許你希望其中的一個欄位的的值為一個預定義的值類表中的一個. 比方說, 在SearchRequest訊息中
你想定義一個 corpus
欄位, corpus欄位的值可以為:" UNIVERSAL
, WEB
, IMAGES
, LOCAL
, NEWS
, PRODUCTS
或者 VIDEO"
.
你可以非常簡單的給你的訊息新增一個列舉型別 - 一個列舉欄位型別其值指定被指定為一個常量的集合 (如果你嘗試賦值一個不一樣的值, 解析器將會認為這個欄位為未知欄位). 在下面的例子中 我們給corpus欄位指定為列舉型別與其可能的值 :
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。否則如果出現別名的話編譯器將會報錯!
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; //不註釋這行的話會引發一個錯誤異常
列舉值的範圍必須在32位整數之內.列舉值的編碼使用可變長度的整數,負數會非常低效所以,不推薦使用。你可以在一個訊息內部定義一個列舉型別,比如上面的例子。或者也可以在訊息的外部定義。這些列舉型別是可以在.proto檔案內中重用的,你可以在訊息內定義個列舉型別。然後在不同的訊息型別中使用它!可以使用MessageType.EnumType來訪問。
當你執行編譯器編譯.proto檔案中的列舉型別時,生成的程式碼會有一個相對應的列舉值(JAVA
或者C++),或者有一個特別的EnumDescriptor類(python)用於在執行時生成一個符號常量集合。
待續....................