1 Protocol Buffers的介绍
Protocol Buffers是一种用于序列化结构化数据的机制,它具有灵活、高效、自动化的特点。类似于XML,但是比XML更小巧、快捷、简单。在Google 几乎所有它内部的RPC协议和文件格式都是采用PB。
PB具有以下特点:
平台无关、语言无关
高性能 比XML块20-100倍
体积小 比XML小3-10倍
使用简单
兼容性好
在这里,我做了个小实验,将一个29230KB的自定义格式的文本数据转换成PB和XML:
PB XML转换后的大小 21011KB 43202KB
解析时间(100次循环) 18610ms 169251ms
完成解析所写代码行数 1行 50行
与官方说法的差距,主要可能是因为应用场景不同,我的测试数据中字段比较长
表1:PB与XML的实验比较
可见,PB作为一种轻量级的数据协议,在时间、空间上都有一定的优势。
2 Protocol Buffers的简单应用 2.1 创建流程 2.1.1 定义一个.proto文件新建一个文件,命名为addressbook.proto,内容如下:
package tutorial;//命名空间
option java_package = "com.example.tutorial";//生成文件的包名
option java_outer_classname = "AddressBookProtos";//类名
message Person { //要描述的结构化数据
required string name = 1;//required表示这个字段不能为空
required int32 id = 2;//等号后面的内容为数字别名
optional string email = 3;//optional表示可以为空
PhoneNumber {//内部message
required string number = 1;
optional int32 type = 2;
}
repeated PhoneNumber phone = 4
}
message AddressBook {
repeated Person person = 1;//是个集合
}
对以上内容的一点解释:
2.1.2 使用PB编译器输入:protoc -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto
其中 -I指定.proto文件所在目录
–java_out指定生成java文件所在的目录
经过以上步骤,会在指定的$DST_DIR目录下生成一个AddressBookProtos.java的类。在maven中引入protobuf-java这个依赖后,利用这个类,便能序列化/反序列化数据了。
生成的代码结构如下:
class AddressBookProtos{
class Person{
class PhoneNumber{class Builder{} }
class Builder{}
}
class AddressBook{class Builder{} }
}
可以看到Person、PhoneNumber、AddressBook这些内部类则对应了所定义的那些message。
2.2 序列化数据及分析通过阅读代码可以看到,以上三个类的成员变量都是private类型的,并且,只提供了getter方法,而没有提供setter方法去为数据变量赋值。
PB利用了内部类可以访问到外部类中私有成员变量的特性。对外部类的任何赋值操作都需要通过Builder内部类来进行。Builder中有一个指向外部类的引用(名为result),当赋值完成,调用Builder的build()方法时,会把这个对象返回,同时使result指向null。
PB通过这样一种方式保证了数据安全性,一旦数据构建完毕,将无法再对其进行修改。
拿PhoneNumber这个类来说,对成员变量number、type赋值,需要以如下方式来进行:
PhoneNumber.Builder builder = PhoneNumber.newBuilder();
//调用setter赋值,setter返回了this,所以可以链式表述
builder.setNumber("111").setType(1);
//赋值完成后,调用Builder的build方法,将返回PhoneNumber对象
PhoneNumber phoneNumber = builder.build();
构建完成后,可以调用writeTo方法,将数据写入数据流中。
2.3 反序列化及分析一行代码便能完成反序列化:
AddressBook list = AddressBook .parseFrom(inputStream或buffer);
背后PB做了很多事情:
根据inputStream或者buffer去构造一个CodedInputStream;
然后使用生成代码中的mergeFrom方法,去解析二进制数据:
首先调用CodedInputStream的readTag,也就是从中取得key值(int类型),然后通过swtich块来往对象中赋值(PB采用了Base 128 Varints的方式来编码这个数字,后面会介绍这种方式的)。
将数据解析完成后,会调用build()方法,将构建好的对象返回。