http://hi.baidu.com/austincao/item/1663f219d96f3c1fe2f986f4
這個鏈接是 protobuf for java 的入門教程,我們可以拿來參考學習;
Protobuf最好的入門教程(一)[毫不猶豫的轉了]
1、在一個.proto文件中定義一個信息格式.
2、使用protoc命令進行編譯,生成java代碼.
3、使用Java protocol buffer API進行讀寫操作.
l 定義proto文件
以一個地址薄爲例,從建立一個.proto文件開始,爲需要序列化的數據接口加入一個message屬性,在message裏面,爲每一個字段指定名稱和類型,如下所示:
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
正如你所見, c++和Java中message定義的語法類似,下面我們來看看每個部分的意義:
爲了避免命名衝突,.proto文件以包聲明開始,在java中除了特別指定一個java_package屬性,否則包名一般爲Java的包。正像上面的 例子,雖然提供了java_package屬性,你通常還是應該定義package屬性以避免在ProtocolBuffers中命名衝突。包聲明以後, 有兩個Java屬性:java_package和java_outer_classname。java_package表示生成的Java代碼的包,如果 沒有指定,編譯器會根據package屬性確定包名。java_outer_classname屬性定義生成文件的類名。如果沒有指定,會根據文件名進行 轉換,如:"my_proto.proto"缺省會使用MyProto作爲外部類名。
接下來是定義message屬性,一個message是包含了各種類型字段的聚集。有很多標準的變量類型可以使用,包 括:bool,int32,float,double和string。你也可以使用其他的message作爲字段類型。正像例子中的Person包含了 PhoneNumber,而AddressBook包含了Persion。甚至可以在message內部定義message,例 如:PhoneNumber就是在Persion裏面定義的。你還可以定義enum類型,正像指定電話號碼類型的MOBILE、HOME、WORK。
其中“=1”,“=2”表示每個元素的標識號,它會用在二進制編碼中對域的標識。標識號1-15由於使用時會比那些高的標識號少一個字節,從最優化 角度考慮,可以將其使用在一些較常用的或repeated元素上,對於16以上的則使用在不常用的或optional的元素上。對於repeated的每 個元素都需要重複編碼該標識號,所以repeated的域進行優化來說是最顯示的。
每個字段必須提供一個修飾詞:
Ø required:表示字段必須提供,不能爲空。否則message會被認爲是未初始化的,試圖build未初始化的message會拋出 RuntimeException。解析未初始化的message會拋出IOException。除此之外,一個required字段與optional 字段完全相同。
Ø optional:可選字段,可以設置也可以不設置。如果沒有設置,會設置一個缺省值。可以指定一個缺省值,正像電話號碼的type字段。否則,使用系統 的缺省值:數字類型缺省爲0;字符類型缺省爲空串;邏輯類型缺省爲false;對於嵌入的message,缺省值通常是message的實例或原型。
Ø repeated:字段可以被重複(包括0),可等同於動態數組或列表。其中存儲的值列表的順序是被保留的。
Required修飾的字段是永久性的,在使用該修飾符時一定要特別小心。如果在以後想要修改required域爲optional域時會出現問 題。對於訪問舊接口的用戶來說沒有該字段時,將會認爲是不合法的訪問,將會被拒絕或丟棄。其中google的一些工程師給出的建議是如果不是必須,就儘量 少用required修飾符。
l 編譯Protocol Buffers文件
既然現在已經有了.proto文件,接下來就需要利用編譯器protoc對.proto文件進行編譯,生成具體的java類。就可以讀取及寫入AddressBook、Person及PersonNumber消息了。
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
$SRC_DIR :表示.proto文件所在目錄;$DST_DIR:生成的java代碼的文件夾。
編譯成功後,會在指定的目錄下生成Java代碼文件,包含了對屬性的操作,下一步就可以通過API進行數據的讀寫了。
l Protocol Buffer API使用
接下來具體看一下所生成的java代碼及其中的方法。在AddressBookProtos.java中可以看出,其中的內部類對應的是addressbook.proto中定義的格式。每個類都有它自己的Builder類,通過它即可以創建該類的實例。你可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/javatutorial.html#builders中查閱到更多關於builder的信息。
Messages和Builders都會爲每個域創建自動的訪問方法,其中messages只有getters,而builders有getters和setters。下面是Person類message的訪問方法:
// required string name = 1;
public boolean hasName();
public String getName();
// required int32 id = 2;
public boolean hasId();
public int getId();
// optional string email = 3;
public boolean hasEmail();
public String getEmail();
// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);
Person類builder的訪問方法(Person.Builder):
// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName();
// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();
// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();
// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);
public Builder setPhone(int index, PhoneNumber value);
public Builder addPhone(PhoneNumber value);
public Builder addAllPhone(Iterable<PhoneNumber> value);
public Builder clearPhone();
正如你所見,對於每個域都有簡單的javabean風格的getters和setters。對於具有單一值的類型,有has方法用來表示該值是否有設置。當然也可以通過clear方法來將該字段的值清空。
重複域也有額外的方法,如count方法用來統計當前重複域的大小,getters和setters用於根據索引來獲取或設置值。add方法用於將一個新元素添加到重複域中,addAll方法則將一組元素添加到重複域中。
上述示例中訪問方法的名稱採用了駝峯式命名,對應在.proto文件中採用的是小寫字母+下劃線的命名。這種轉換是由protoc編譯器自動完成的,我們只需要按照這種規約定義.proto文件即可。
l 枚舉和內部類
生成的代碼包含了一個枚舉類型PhoneType,它屬於Person的內部類:
public static enum PhoneType {
MOBILE(0, 0),
HOME(1, 1),
WORK(2, 2),
;
...
}
PhoneNumber也是作爲Person的一個內部類而產生的。
l Builders 對Messages
由編譯器自動生成的message類是不可變的,一旦一個message對象構建以後,就象java中的String類一樣是不可變的。創建一個message時,必須首先創建一個builder,設置必須的一些值後,再調用builder的build()方法。
也許你已經注意到了,builder的每個方法在消息修改後又會返回builder,這個返回對象又可以調用其它方法。這種方式對於在同一行操作不同的方法提供了便利。如下的代碼示例,創建一個Person實例。
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("[email protected]")
.addPhone(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
l 標準的Message方法
對於每個message或builder類也包含一些方法用於檢查或操作整個消息,如:
· isInitialized():檢查是否所有的required字段已經設置了值;
· toString():返回一個易於閱讀的消息結果,對於調試來說非常有用;
· mergeFrom(Message other): 將其它內部merger到當前的消息中,重寫單一值域或者新增repeated域,僅用於builder。
· clear():將所有域清空設置,僅用於builder。