本系列文章统一使用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,其可读性差、缺乏自我描述,在通用性上也不及后两者普及,但它具有更高的效率,体积更小,并且能够自动生成对应语言的特殊源代码便于开发。