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;//是个集合

}

对以上内容的一点解释:

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()方法,将构建好的对象返回。 

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/bc58e43442d700ba6a092f06b0d7bcc9.html