Protocol Buffers的应用与分析
Protocol Buffers的应用与分析
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
;
//是个集合
}
对以上内容的一点解释:
- PB所支持的元类型数据请参考:PB元类型数据
- 修饰符required:这个修饰符应该谨慎使用,滥用会导致后续的修改容易出现兼容性问题;
- 修饰符optional:对于常出现的属性,为节省空间应该取1-16的别名;
- PB是以key-value的形式来将结构化数据序列化的。它采用了将等号后的数字别名以及属性的类型用varints编码成一个数字,来作为key。
2.1.2 使用PB编译器
输入:protoc -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto
其中 -I指定.proto文件所在目录
–java_out指定生成java文件所在的目录
2.1.3 使用PB的API来写入和读取messages
经过以上步骤,会在指定的$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()方法,将构建好的对象返回。
更多详情见请继续阅读下一页的精彩内容:
|
评论暂时关闭