本系列文章统一使用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_java 和 list_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 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 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; string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; } 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,其可读性差、缺乏自我描述,在通用性上也不及后两者普及,但它具有更高的效率,体积更小,并且能够自动生成对应语言的特殊源代码便于开发。