protobuf java基础
1:定義proto文件:
??以一個地址薄為例,從建立一個.proto文件開始,為需要序列化的數據接口加入一個message屬性,在message里面,為每一個字段指定名稱和類型(算是IDL吧),如下所示:
package demo; option java_package = "com.sunchao.serializer.testSerializer.protobuf"; option java_outer_classname = "PersonProto"; message Person {required string name = 1;required int32 id = 2;optional string email = 3;enum PhoneType {MOBILE = 0;HOME = 1;WORK = 3;}message PhoneNumber {required string number = 1;optional PhoneType type = 2 [default = HOME];}repeated PhoneNumber phone = 4;message CountryInfo {required string name = 1;required string code = 2;optional int32 number = 3;} }message AddressBook {repeated Person person = 1;}下面我們來看看每個部分的意義:
???? 為了避免命名沖突,.proto文件以包聲明開始(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修飾符。
?
2:Protocol Buffer API使用
? ? ?接下來具體看一下所生成的java代碼及其中的方法。在AddressBookProtos.java中可以看出,其中的內部類對應的是addressbook.proto中定義的格式。每個類都有它自己的Builder類,通過它即可以創建該類的實例。
? ? ?Messages和Builders都會為每個域創建自動的訪問方法,其中messages只有getters,而builders有getters和setters。下面是Person類message的訪問方法:
// required string name = 1;boolean hasName();String getName();// required int32 id = 2;boolean hasId();int getId();// optional string email = 3;boolean hasEmail();String getEmail();// repeated .demo.Person.PhoneNumber phone = 4;java.util.List<com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumber> getPhoneList();com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumber getPhone(int index);int getPhoneCount();java.util.List<? extends com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumberOrBuilder> getPhoneOrBuilderList();com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumberOrBuilder getPhoneOrBuilder(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文件即可。
?
3:枚舉和內部類
public static PhoneType valueOf(int value) {switch (value) {case 0: return MOBILE;case 1: return HOME;case 3: return WORK;default: return null;}}? ? PhoneNumber也是作為Person的一個內部類而產生的。
public static final class PhoneNumber extendscom.google.protobuf.GeneratedMessageimplements PhoneNumberOrBuilder?
4:Builders 對Messages
? ? ?由編譯器自動生成的message類是不可變的(final),一旦一個message對象構建以后,就象java中的String類一樣是不可變的。創建一個message時,必須首先創建一個builder,設置必須的一些值后,再調用builder的build()方法。
? ? ?也許你已經注意到了,builder的每個方法在消息修改后又會返回builder,這個返回對象又可以調用其它方法。這種方式對于在同一行操作不同的方法提供了便利。如下的代碼示例,創建一個Person實例。
Person john =Person.newBuilder().setId(1234).setName("John Doe").setEmail("jdoe@example.com").addPhone(Person.PhoneNumber.newBuilder().setNumber("555-4321").setType(Person.PhoneType.HOME)).build();?
5:標準的Message方法
? ? ?對于每個message或builder類也包含一些方法用于檢查或操作整個消息,如:
? ? ? ? ??isInitialized():檢查是否所有的required字段已經設置了值;
? ? ? ? ??toString():返回一個易于閱讀的消息結果,對于調試來說非常有用;
? ? ? ? ??mergeFrom(Message other): 將其它內部merger到當前的消息中,重寫單一值域或者新增repeated域,僅用于builder。
? ? ? ? ??clear():將所有域清空設置,僅用于builder。
?
6:解析及序列化
?
? ? ?最終,protocol buffer類就可以通過一些方法來完成消息的讀寫入及讀取。如:
?
? ? ? ? ?byte[] toByteArray():消息序列化并返回一個字節數組;
?
? ? ? ? ?static Person parseFrom(byte[] data):從一個特定的字節數組解析成消息;
?
? ? ? ? ?void writeTo(OutputStream output):序列化消息并將其寫入到OutputStream中;
?
? ? ? ? ?static Person parseFrom(InputStreaminput):從InputStream流中讀取并解析消息。
?
? ? 上述提供的僅僅是解析及序列化的一組接口,可以在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/java/com/google/protobuf/Message.html中查閱更全面的的接口。
?
7:寫入消息
? ? ?接下來先看如何來用protocol buffer類,對于地址薄應用首先需要將個人資料寫入地址薄中。為了做到這些,需要創建protocol buffer類并將信息寫入。程序設計如下,會先從一個文件讀取AddressBook信息,通過用戶手工輸入一個Person的信息,交將其回寫至 AddressBook文件中。代碼示例如下:
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.PrintStream;import com.sunchao.serializer.testSerializer.protobuf.PersonProto.AddressBook; import com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person;public class TestAddress {static Person prompteForAddress(BufferedReader stdin,PrintStream stdout) throws Exception {Person.Builder person = Person.newBuilder();stdout.print("Enter person ID: ");person.setId(Integer.valueOf(stdin.readLine()));stdout.print("Enter name: ");person.setName(stdin.readLine());stdout.print("Enter email address(blank for none): ");String email = stdin.readLine();if (email != null && email.length() > 0){person.setEmail(email);}while (true){stdout.print("Enter a phone number(or leave blank to finish): ");String number = stdin.readLine();if (number == null || number.length() == 0) {break;} Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber.newBuilder();phoneNumber.setNumber(number);stdout.print("Is this a mobile, home, or work phone?(please enter in home|work|mobile):");String type = stdin.readLine();if ("home".equalsIgnoreCase(type)) {phoneNumber.setType(Person.PhoneType.HOME);} else if ("work".equalsIgnoreCase(type)) {phoneNumber.setType(Person.PhoneType.WORK);} else if ("mobile".equalsIgnoreCase(type)) {phoneNumber.setType(Person.PhoneType.MOBILE);} else {stdout.println("unknown phone type! using default.");}person.addPhone(phoneNumber);}return person.build();}public static void main(String args[]) throws Exception {if (args.length != 1){System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE");System.exit(-1);}AddressBook.Builder book = AddressBook.newBuilder();try {book.mergeFrom(new FileInputStream(args[0]));} catch (FileNotFoundException e) {System.out.println(args[0] + ": File not found. Create a new file.");} book.addPerson(prompteForAddress(new BufferedReader(new InputStreamReader(System.in)),System.out));FileOutputStream output = new FileOutputStream(args[0]);try {book.build().writeTo(output);} finally {output.close();}} }?
8:讀取消息
?
import java.io.FileInputStream;import com.sunchao.serializer.testSerializer.protobuf.PersonProto.AddressBook; import com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person; import com.sunchao.serializer.testSerializer.protobuf.PersonProto.Person.PhoneNumber;public class ListPerson {static void print(AddressBook addressBook){for (Person person : addressBook.getPersonList()){System.out.println("Person ID: " + person.getId());System.out.println("Person Name: " + person.getName());if (person.hasEmail()) {System.out.println(person.getEmail());}for (PhoneNumber number : person.getPhoneList()){switch (number.getType()) {case HOME :System.out.print(" Home Phone #: " + number.getNumber());break;case MOBILE :System.out.print(" Mobile Phone #: " + number.getNumber());break;case WORK:System.out.print(" Work Phone #: " + number.getNumber());break;default :System.out.print(" Unknown Phone type #: " + number.getNumber());break;}}System.out.println("********************************************");}}public static void main(String args[]) throws Exception {if (args.length != 1) {System.err.println("Usage: ListPeople Address <file-option>:");System.exit(-1);}AddressBook addressBook = AddressBook.parseFrom(new FileInputStream(args[0]));print(addressBook);} }?
?
9:對Protocol Buffer進行擴展
? ? ?有時會發現在發布完protocolbuffer代碼后,需要對其進行擴展升級。如果想讓新代碼向后兼容,而且老代碼能夠向前兼容,此時需要遵循以下的規則。
? ? ? ? ?不能改變已存在域的標識號;
? ? ? ? ?不要任意添加或刪除required修飾的域;
? ? ? ? ?可以刪除optional或repeated修飾的域;
? ? ? ? ?可以新增optional或repeated修飾的域,但是必須使用新的標識號。
? ? ? 如果按照上述規約進行了升級,舊的代碼將可以讀取新的消息并將一些新的字段忽略掉。對于舊代碼,被刪除的optional域將會使用其默認值,刪除的repeated域將會被置空。新代碼中也將能夠透明地讀取舊的消息,但是有一點需要明確,那就是新的optional域不能出現在舊消息中,可以通過 has方法進行明確檢查,或者在.proto文件中為該字段提供一個默認值。如果一個optional元素沒有明確的聲明默認值的話,則會根據其類型取默 認值,如:字符串類型,取空串為默認值;布爾類型取false為其默認值;數字類型取0為其默認值。如果新增了一個repeated域,新代碼將不能判斷 其是否是空,老代碼也不會設置其值,且它并沒有has方法。
?
?
轉載于:https://www.cnblogs.com/onlysun/p/4569462.html
總結
以上是生活随笔為你收集整理的protobuf java基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Moving Average
- 下一篇: iOS: 让自定义控件适应Autolay