当大多数公司还在纠结于如何更好提高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编译器的版本号。
【android|Protobuf的那些事】那再用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 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 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 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("jdoe@example.com")
.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