protobuf for java 的使用

http://hi.baidu.com/austincao/item/1663f219d96f3c1fe2f986f4     

這個鏈接是 protobuf  for java 的入門教程,我們可以拿來參考學習;


Protobuf最好的入門教程(一)[毫不猶豫的轉了]

本文檔爲java編程人員使用protocol buffer提供了一個基本的介紹,通過一個簡單的例程進行介紹。通過本文,你可以瞭解到如下信息:

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。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章