Google Protocol Buffer

网友投稿 2490 2022-05-30

Google Protocol Buffer(protobuf)是一种高效且格式可扩展的编码结构化数据的方法。和JSON不同,protobuf支持混合二进制数据,它还有先进的和可扩展的模式支持。protobuf已在大多数软件平台上实现,包括适用于Android的精简Java版。

http://developers.google.com/protocol-buffers/上有protobuf文档,下载链接以及安装说明。需要注意的是,Android平台为构建精简版的protobuf,所以不能使用中央Maven仓库里的版本。在Java源码目录内执行mvn package -p lite可以生成精简版。检查是否有更多安装细节。

JSON允许对JSONObject对象进行任意数据的读写操作,但protobuf要求使用模式来定义要存储的数据。模式会定义一些消息,每个消息包含一些名-值对字段。字段可能是内置的原始数据类型,枚举或者其他消息。可以指定一个字段是必须的还是可选的,以及其他一些参数。一旦定义好模式,就可以使用protobuf工具生成Java代码。生成的Java类现在可以很方便地用来读写protobuf数据。

下面的代码使用protobuf模式定义了Task信息:

package com.aptl.code.task;

option optimize_for = LITE_RUNTIME;

option java_package = "com.aptl.protobuf";

option java_outer_classname = "TaskProtos";

message Task {

enum Status {

CREATED = 0;

ONGOING = 1;

CANCELLED = 2;

COMPLETED = 3;

message Owner {

required string name = 1;

optional string email = 2;

optional string phone = 3;

}

message Comment {

required string author = 1;

required uint32 timestamp = 2;

required string content = 3;

}

required string name = 1;

required uint64 created = 2;

required int32 priority = 3;

required Status status = 4;

optional Owner owner = 5;

repeated Comment comments = 6;

}

这里将给出以上消息定义的关键性说明。

1. message是消息定义的关键字,等同于C++中的struct/class,或是Java中的class。

2. Task 为消息的名字,等同于结构体名或类名。

3. required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值。与此同时,在Protocol Buffer中还存在另外两个类似的关键字,optional和repeated,带有这两种限定符的消息字段则没有required字段这样的限制。相比于optional,repeated主要用于表示数组字段。具体的使用方式在后面的用例中均会一一列出。

4. int64和string分别表示长整型和字符串型的消息字段,在Protocol Buffer中存在一张类型对照表,既Protocol Buffer中的数据类型与其他编程语言(C++/Java)中所用类型的对照。该对照表中还将给出在不同的数据场景下,哪种类型更为高效。该对照表将在后面给出。

5. name,created ,priority ,status,owner 和comments分别表示消息字段名,等同于Java中的域变量名,或是C++中的成员变量名。

6. 标签数字1和2则表示不同的字段在序列化后的二进制数据中的布局位置。在该例中,created字段编码后的数据一定位于name之后。需要注意的是该值在同一message中不能重复。另外,对于Protocol Buffer而言,标签值为1到15的字段在编码时可以得到优化,既标签值和类型信息仅占有一个byte,标签范围是16到2047的将占有两个bytes,而Protocol Buffer可以支持的字段数量则为2的29次方减一。有鉴于此,我们在设计消息结构时,可以尽可能考虑让repeated类型的字段标签位于1到15之间,这样便可以有效的节省编码后的字节数量。

Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:

1. 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。

2. 消息级别,这样的选项仅影响某个消息及其包含的所有字段。

3. 字段级别,这样的选项仅仅响应与其相关的字段。

下面将给出一些常用的Protocol Buffer选项。

1. option java_package = "com.aptl.protobuf";

java_package是文件级别的选项,通过指定该选项可以让生成Java代码的包名为该选项值,如上例中的Java代码包名为com.aptl.protobuf。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/aptl/protobuf子目录中。如果没有指定该选项,Java的包名则为package关键字指定的名称。该选项对于生成C++代码毫无影响。

2. option java_outer_classname = "TaskProtos";

java_outer_classname是文件级别的选项,主要功能是显示的指定生成Java代码的外部类名称。如果没有指定该选项,Java代码的外部类名称为当前文件的文件名部分,同时还要将文件名转换为驼峰格式,如:my_project.proto,那么该文件的默认外部类名称将为MyProject。该选项对于生成C++代码毫无影响。

注:主要是因为Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C++则不存在此限制。因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static com.aptl.protobuf.TaskProtos.*。

3. option optimize_for = LITE_RUNTIME;

optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME。缺省情况下是SPEED。

SPEED: 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。

CODE_SIZE: 和SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。

LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。

注:对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message。

4. [pack = true]: 因为历史原因,对于数值型的repeated字段,如int32、int64等,在编码时并没有得到很好的优化,然而在新近版本的Protocol Buffer中,可通过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:

repeated int32 samples = 4 [packed=true]。

注:该选项仅适用于2.3.0以上的Protocol Buffer。

5. [default = default_value]: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:

optional int32 result_per_page = 3 [default = 10]。

从InputStream反序列化protobuf对象非常容易,如下例所示。生成的Java代码提供一些用于合并字节数组,byteBuffer和InputStream对象的函数。

public static TaskProtos.Task readBrotoBufFromStream(InputStream inputStream)

throws IOException {

TaskProtos.Task task = TaskProtos.Task.newBuilder()

.mergeFrom(inputStream).build();

Log.d("ProtobufDemo", "Read Task from stream: "

+ task.getName() + ", "

+ new Date(task.getCreated()) + ", "

+ (task.hasOwner() ?

task.getOwner().getName() : "no owner") + ", "

+ task.getStatus().name() + ", "

+ task.getPriority()

+ task.getCommentsCount() + " comments.");

return task;

}

本例显示了如何检索protobuf对象的值。注意:protobuf对象是不可变的。修改它们唯一的方法是从现有对象创建一个新的构建器,设置新的值,并生成一个取代原有对象的Task。这使得protobuf有点不好用,但它强制开发者在持久化复杂对象时使用更好的设计。

下面的方法显示了如何构建一个新的protobuf对象。首先为构造的对象创建一个新的Builder,然后设置所需要的值并调用Builder.build()方法来创建不可变的protobuf对象。

public static TaskProtos.Task buildTask(String name, Date created,

String ownerName, String ownerEmail,

String ownerPhone,

TaskProtos.Task.Status status,

int priority,

List comments) {

TaskProtos.Task.Builder builder = TaskProtos.Task.newBuilder();

builder.setName(name);

builder.setCreated(created.getTime());

builder.setPriority(priority);

builder.setStatus(status);

if(ownerName != null) {

TaskProtos.Task.Owner.Builder ownerBuilder

= TaskProtos.Task.Owner.newBuilder();

ownerBuilder.setName(ownerName);

if(ownerEmail != null) {

Google Protocol Buffer

ownerBuilder.setEmail(ownerEmail);

}

if(ownerPhone != null) {

ownerBuilder.setPhone(ownerPhone);

}

builder.setOwner(ownerBuilder);

}

if (comments != null) {

builder.addAllComments(comments);

}

return builder.build();

}

API提供了一系列方法用来把protobuf对象写到文件或者网络流中。下面的代码演示了如何把Task对象序列化到OutputStream中。

public static void writeTaskToStream(TaskProtos.Task task,

OutputStream outputStream)

throws IOException {

task.writeTo(outputStream);

}

protobuf主要的优点是它比JSON消耗的内存少,而且读写速度更快。protobuf对象还是不可变的,如果要确保对象的值在整个生命周期中保持不变,该特性会非常有用。

Protocol Buffers 2.6.1 full source: protobuf-2.6.1.tar.gz (MD5: f3916ce13b7fcb3072a1fa8cf02b2423)

Protocol Compiler 2.6.1 binary for windows: protoc-2.6.1-win32.zip (MD5: b057f86ef83835010bb227eb2d82de04)

C++ Java

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【云驻共创】一文教你全方位揭秘Ajax指南
下一篇:8个流行的Python可视化工具包。
相关文章