• 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. 标签数字12则表示不同的字段在序列化后的二进制数据中的布局位置。在该例中,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对象还是不可变的,如果要确保对象的值在整个生命周期中保持不变,该特性会非常有用。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    转 | 禁忌搜索算法(Tabu Search)求解带时间窗的车辆路径规划问题详解(附Java代码)
    Branch and price and cut求解传统VRP问题以及VRPTW问题
    标号法(label-setting algorithm)求解带时间窗的最短路问题(ESPPRC)
    运筹学从何学起?如何快速入门精确式算法?
    转 | 模拟退火算法(SA)和迭代局部搜索(ILS)求解TSP的Java代码分享
    用Python画论文折线图、曲线图?几个代码模板轻松搞定!
    45. 截取“测试数据”后面的内容
    44. 更改oracle字符集编码american_america.zh16gbk 改为 SIMPLIFIED CHINESE_CHINA.ZHS16GBK
    18. 浏览器关闭页面时弹出“确定要离开此面吗?”
    6. concat_ws用法
  • 原文地址:https://www.cnblogs.com/liyuanjinglyj/p/4656566.html
Copyright © 2020-2023  润新知