从零开始玩转Protobuf之一 - 准备工作

Author Avatar
Nemo 2017-02-08 11:22:12
  • 在其它设备中阅读本文章

本系列文章统一使用JAVA并在MacOS上进行演绎,如遇错误请指出,期望共同学习进步

0x00 Protobuf简介

Protobuf全称为Google Protocol Buffer,它是一种轻便高效的结构化数据存储格式,主要用于结构化数据串行化,很适合做数据存储或RPC数据交换格式,相比XML它更小更快更简单,它的处理时间开销、空间开销更小。

首次使用时定义一种结构化数据的格式即 .proto文件 ,然后使用Protocol Compiler根据此结构生成对应语言的特殊源代码,之后便能轻松地向各种数据流读写结构化数据,在后期修改了数据格式之后也不会影响旧版本的程序。

0x01 下载并编译Protocol Compiler

首先下载对应的Protocol Compiler,用于编译 .proto文件 和生成给定语言操作数据流的代码。

书写这篇文章时,Protobuf的最新版为3.2.0,可到如下地址获取相应语言的最新版本:https://github.com/google/protobuf/releases

由于Protocol编译器是用C++开发的,需提前安装make、g++等相关工具,安装方法请自行Google。

下载最新Release包:

1
2
3
wget https://github.com/google/protobuf/releases/download/v3.2.0/protobuf-java-3.2.0.tar.gz
tar -xzf protobuf-java-3.2.0.tar.gz
cd protobuf-java-3.2.0

编译并安装:

1
2
3
4
5
configure ## 编译三连
make
make install
protoc --version ## 测试Protocol编译器是否安装成功

其他各平台的详细安装方法参见protobuf-3.2.0/src/README.md

0x02 编译Java Runtime Library

安装Maven

Protobuf的Java项目采用maven进行管理,先安装maven,若已有maven环境忽略此步骤。

1
2
3
brew install maven ## 使用brew进行安装
mvn test ## 安装完maven后测试

使用maven安装依赖并打包

1
2
3
cd protobuf-3.2.0/java/
mvn install
mvn package

配置CLASSPATH

完成上述步骤后将会在protobuf-3.2.0/java/core/target目录生成protobuf-java-3.2.0.jar包,我们将protobuf-java-3.2.0.jar加入到CLASSPATH中:

1
2
3
4
export CLASSPATH={PATH_TO_JARBALL}/protobuf-3.2.0/java/core/target/protobuf-java-3.2.0.jar:$CLASSPATH ## 请修改{PATH_TO_JARBALL}目录
echo 'CLASSPATH={PATH_TO_JARBALL}/protobuf-3.2.0/java/core/target/protobuf-java-3.2.0.jar:$CLASSPATH' > ~/.profile ## 也可将上述代码加入~/.profile文件中
source ~/.profile

到此就完成了前置依赖的安装,下面我们来真正认识protobuf。

0x03 编译并运行示例

执行如下命令编译示例代码:

1
2
cd protobuf-3.2.0/examples/
make java

如果一切正常将会在当前目录生成 add_person_javalist_people_java 两个shell脚本,其中 add_person_java 用于添加人员信息并存储到文件中,list_people_java 从文件中读取人员信息。

执行add_person_java脚本录入一些信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
sh ./add_person_java
Usage: AddPerson ADDRESS_BOOK_FILE
sh ./add_person_java ./person.data
./person.data: File not found. Creating a new file.
Enter person ID: 1
Enter name: Nemo
Enter email address (blank for none): [email protected]
Enter a phone number (or leave blank to finish): 18012345678
Is this a mobile, home, or work phone? mobile
Enter a phone number (or leave blank to finish): 86260000
Is this a mobile, home, or work phone? home
Enter a phone number (or leave blank to finish):

程序将会在当前目录生成 person.data 文件,这个就是采用protobuf结构化之后存储的数据文件。 执行list_people_java脚本查看之前录入的数据:

1
2
3
4
5
6
sh ./list_people_java ./person.data
Person ID: 1
Name: Nemo
E-mail address: [email protected]
Mobile phone #: 18012345678
Home phone #: 86260000

0x04 示例代码简析

上面我们完整的演示了Protobuf将结构化数据写入文件和从文件中读取结构化数据的示例,那么这过程是如何完成的呢?

示例中使用了 addressbook.proto 文件定义了相应的包名、类名、以及Person和AddressBook两种协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package tutorial;
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}

Protobuf会根据 addressbook.proto 文件中的内容生成相应的java class,即protobuf-3.2.0/examples/com/example/tutorial/AddressBookProtos.java文件,此AddressBookProtos类包含了一系列Person和AddressBook类的操作,用于读取和写入数据,可以理解为Protobuf根据定义的数据结构封装的数据操作。

add_person 主要是将用户输入存储到文件,addPeople()方法增加people数据,build()方法将数据序列化,核心代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
AddressBook.Builder addressBook = AddressBook.newBuilder();
try {
FileInputStream input = new FileInputStream(args[0]);
try {
addressBook.mergeFrom(input);
} finally {
try { input.close(); } catch (Throwable ignore) {}
}
} catch (FileNotFoundException e) {
System.out.println(args[0] + ": File not found. Creating a new file.");
}
addressBook.addPeople(
PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
System.out));
FileOutputStream output = new FileOutputStream(args[0]);
try {
addressBook.build().writeTo(output);
} finally {
output.close();
}

list_people 从文件中读取已序列化的二进制数据,getPeopleList()获取people列表数据,getId()和getName()为获取属性值方法,核心代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for (Person person: addressBook.getPeopleList()) {
System.out.println("Person ID: " + person.getId());
System.out.println(" Name: " + person.getName());
if (!person.getEmail().isEmpty()) {
System.out.println(" E-mail address: " + person.getEmail());
}
for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
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());
}
}

0x05 总结

虽然Protobuf相比于XML和Json,其可读性差、缺乏自我描述,在通用性上也不及后两者普及,但它具有更高的效率,体积更小,并且能够自动生成对应语言的特殊源代码便于开发。