當大多數公司還在糾結於如何更好提高MobileAPI的性能時,有的公司已經開始拋棄http + json,開始走向tcp + protobuf的路線了。
那麼什麼是protobuf呢:protobuf是一種基於二進制的協議,它能夠非常快速高效的序列化數據,考慮之前的xml,但是它的體積要比xml小的多得多。當然使用它還有其他非常之多的理由。我們考慮java的情況,傳統的序列化方法就是實現Serialization,不過它帶來非常之多的煩惱,比如版本兼容,這會帶來非常之大的測試壓力,且它的處理完的數據並不能跨語言的分享啊,python or c++都不能夠使用,這是很頭疼的。除此之外,我們序列化數據的時候我們會使用一些ad-hoc way,比如會把數據都放在一個字符串裏面,考慮有四個整形數據的例子:"12:3:-23:67",它顯然是足夠簡單的,但是卻需要手寫一些解析,編碼代碼,並且,還有一些運行時損耗。再後者,我們可能會想到使用xml,顯然它是不太好的,不然也就沒有json了。
綜合以上,便是protobuf誕生的理由,現在我們要開始正式學習它了。
在使用之前,我們需要安裝它的庫,編譯器啥的,轉到github :https://github.com/google/protobuf,按照readme裏面下載安裝就行了,之後安裝Java的版本,如果一切都正常的話 在終端裏面輸入protoc --version會看到當前protoc編譯器的版本號。
那再用java來使用protobuf的時候,我們需要三個步驟:
1:定義.proto文件,用來定義你的數據結構
2:使用protoc編譯器進行編譯,導出java代碼
3:使用java api進行數據的讀寫
我這裏考慮創建一個 AddressBook的例子
在這個類中我們存放聯繫簿,練習簿中有你記錄的聯繫人以及他的個人信息,包括手機號碼,姓名啥的
我們先粗略的看下proto文件內容:
是不是似曾相識的感覺,他和c++,java代碼還是很像的。我們逐行分析代碼
第一行是指定版本號,如果沒有,大概編譯器會報如下錯誤:
[libprotobuf WARNING google/protobuf/compiler/parser.cc:547] No syntax specified for the proto file. Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. (Defaulted to proto2 syntax.
注意,它必須放在第一行哦,在下面是包名,他是用來防止Protocol Buffers name space裏的名字衝突,所以還是不省略爲好,再往下是生成java文件後的包名,很顯然,第五行的便是類名了
如果你真的懶得寫這些的話 關於他們缺省值你可以參考官網給出的解釋:
再往下,你可以看到很多關於message的定義,他是用來存放數據域的,數據域有自己的類型,protobuf支持的類型有bool
,
int32
, float
, double
, and string
,包括自定義類型(也爲message)等。比如Person有自己的名字,id,email,和聯繫電話,觀之name,是string類型的,前面還有 required修飾符,表示這個域必須有,後面還有 = 1,這個符號,這是用來在二進制編碼裏面唯一確認當前的域的,值得注意的是1-15要求更少得存儲空間去存儲這個標記數字,也就是說,如果你某些域用的很頻繁,應該首先分配這些數字,這也是作爲優化的一種方式。
往下面讀,可以看到email是optional,也就是可選的,這個域可有可無,如果沒有設置的話,系統會自動填充一個合理地值,比如對於bool 你可以是false,字符串給定一個空值,所以,如果你對編譯器沒有信心,你可以自己給定一個默認的值,考慮第二十行的做法
對於PhoneType,他是一個枚舉類型,枚舉類型裏面也要給定標記數字(Tag number)
通常情況下,一個人不可能只擁有一個號碼,所以phone應該是一個集合,這個集合可以是變化的,可爲空,你可以想象爲一個動態變化的數組,所以它的修飾詞是repeated
總結下:
一個域由以下部分組成:
修飾詞 類型 域名 = 標記值
到目前爲止,所有的基礎內容已經介紹完,然後我們保存下文件,後綴名爲.proto
切換到命令行模式:輸入
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
-I指定源目的文件夾,一般是你的應用源碼位置
--java_out 是生成代碼的目的地 一般和src dir相同
後面一個參數是proto文件的位置
具體的例子:
protoc -I=./ --java_out=./ ./first.proto
便可以在你指定的目標文件夾下面看到生成的Java文件了 :)
The Protocol Buffer API
我們看下Person類:
// 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:
// 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();
可以看到,Builder比Person有更“強的”方法。 每個 clear方法用於重置域到它的初始狀態
重複域有count方法,用於返回這個域的大小
Enums and Nested Classes:
在Person中,我們定義了一個枚舉類型,下面是枚舉類型的Java代碼:
public static enum PhoneType {
MOBILE(0, 0),
HOME(1, 1),
WORK(2, 2),
;
...
}
Builders vs. Messages:
builder使用方式:
Person john =
Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("[email protected]")
.addPhone(
Person.PhoneNumber.newBuilder()
.setNumber("555-4321")
.setType(Person.PhoneType.HOME))
.build();
Message使用方式:
WRITE:
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;
class AddPerson {
// This function fills in a Person message based on user input.
static Person PromptForAddress(BufferedReader stdin,
PrintStream stdout) throws IOException {
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.length() > 0) {
person.setEmail(email);
}
while (true) {
stdout.print("Enter a phone number (or leave blank to finish): ");
String number = stdin.readLine();
if (number.length() == 0) {
break;
}
Person.PhoneNumber.Builder phoneNumber =
Person.PhoneNumber.newBuilder().setNumber(number);
stdout.print("Is this a mobile, home, or work phone? ");
String type = stdin.readLine();
if (type.equals("mobile")) {
phoneNumber.setType(Person.PhoneType.MOBILE);
} else if (type.equals("home")) {
phoneNumber.setType(Person.PhoneType.HOME);
} else if (type.equals("work")) {
phoneNumber.setType(Person.PhoneType.WORK);
} else {
stdout.println("Unknown phone type. Using default.");
}
person.addPhone(phoneNumber);
}
return person.build();
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
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 addressBook = AddressBook.newBuilder();
// Read the existing address book.
try {
addressBook.mergeFrom(new FileInputStream(args[0]));
} catch (FileNotFoundException e) {
System.out.println(args[0] + ": File not found. Creating a new file.");
}
// Add an address.
addressBook.addPerson(
PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
System.out));
// Write the new address book back to disk.
FileOutputStream output = new FileOutputStream(args[0]);
addressBook.build().writeTo(output);
output.close();
}
}
READ:
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
class ListPeople {
// Iterates though all people in the AddressBook and prints info about them.
static void Print(AddressBook addressBook) {
for (Person person: addressBook.getPersonList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
if (person.hasEmail()) {
System.out.println(" E-mail address: " + person.getEmail());
}
for (Person.PhoneNumber phoneNumber : person.getPhoneList()) {
switch (phoneNumber.getType()) {
case MOBILE:
System.out.print(" Mobile phone #: ");
break;
case HOME:
System.out.print(" Home phone #: ");
break;
case WORK:
System.out.print(" Work phone #: ");
break;
}
System.out.println(phoneNumber.getNumber());
}
}
}
// Main function: Reads the entire address book from a file and prints all
// the information inside.
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE");
System.exit(-1);
}
// Read the existing address book.
AddressBook addressBook =
AddressBook.parseFrom(new FileInputStream(args[0]));
Print(addressBook);
}
}
Extending a Protocol Buffer:
如果以後你想升級Proto文件定義,請遵守一下條款:
1:不可以修改現有域的任何標識數字(tag number)
2:不可以添加刪除任何required域
3:你可以刪除optional,repeated域
4:你可以添加optional repeated域,但是你必須要用不同的tag number(之前刪除域的tag number也不能使用!)
protobuf github 包下載: http://download.csdn.net/detail/u013022222/9405304
轉載請註明出處:
http://blog.csdn.net/u013022222/article/details/50521835