Google Protocol Buffer

简介: Google Protocol Buffer

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<TaskProtos.Task.Comment> 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) {
                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)
相关文章
|
存储 编译器 API
Google Protocol Buffer Basics: C++
Google Protocol Buffer Basics: C++
|
机器学习/深度学习 XML 数据格式
|
XML 数据格式 iOS开发
|
存储 C++ 数据格式
Google Protocol Buffer使用经验分享(一) C++动态消息与静态消息的博弈
# 写在前面   相信正在浏览这篇文章的同学,一定已经对PB(Protocol buffer)有所了解,所以这里不罗嗦何为PB了。   我自己从去年年底开始对PB的使用逐渐有一些了解,直到在搜索排序框架(iRank)的重构中尝试应用PB,希望能在“数据结构灵活增删改”和“高效的数据传输反序列化”之间求得平衡。   在这过程之中,对PB 动态消息和静态消息的C++使用方式进行了一些调研
6255 0
|
C++
google proto buffer安装和简单示例
1、安装 下载google proto buff。 解压下载的包,并且阅读README.txt,根据里面的指引进行安装。 $ ./configure $ make $ make check $ make install 没有意外的话,前面三步应该都能顺利完成,第四步的时候,需要root权限。
886 0
|
7月前
|
数据可视化 定位技术 Sentinel
如何用Google Earth Engine快速、大量下载遥感影像数据?
【2月更文挑战第9天】本文介绍在谷歌地球引擎(Google Earth Engine,GEE)中,批量下载指定时间范围、空间范围的遥感影像数据(包括Landsat、Sentinel等)的方法~
2600 1
如何用Google Earth Engine快速、大量下载遥感影像数据?