• Protocol Buffer 学习


    参考博客:https://www.cnblogs.com/dkblog/archive/2012/03/27/2419010.html
    参考博客:https://blog.csdn.net/carson_ho/category_9272167.html
    官网文档:https://developers.google.cn/protocol-buffers/docs/proto
    image.png

    一 基本知识

    1.1 定义

    • 一种 结构化数据 的数据存储格式(类似于 XML、Json )
    • Protocol Buffer 目前有两个版本:proto2 和 proto3
    • https://developers.google.cn/protocol-buffers/
    • 协议缓冲区是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据。
    • 协议缓冲区提供了一种语言中立、平台中立、可扩展的机制,用于以向前兼容和向后兼容的方式序列化结构化数据。
    • 它类似于 JSON,只是它更小更快,并且生成本地语言绑定。
    • 协议缓冲区是定义语言(在 .proto文件中创建)、proto 编译器生成的与数据接口的代码、特定于语言的运行时库以及写入文件(或通过网络连接)。

    1.2 作用

    • 通过将 结构化的数据 进行 串行化(序列化),从而实现 数据存储 / RPC 数据交换的功能
    • 序列化: 将 数据结构或对象 转换成 二进制串 的过程
    • 反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程

    1.3 特点

    1.4 安装

    image.png

    • 验证:protoc --version

    image.png

    • idea 插件安装

    image.png

    二 基本语法

    2.1 包名

    • 防止不同 .proto 项目间命名 发生冲突
    • 每个包会被看作是其父类包的内部类
    package person;
    
    • 1

    2.2 Option选项

    • 作用:影响 特定环境下 的处理方式
    • 在 ProtocolBuffers 中允许 自定义选项 并 使用
    option java_package = "com.carson.proto";
    // 定义:Java包名
    // 作用:指定生成的类应该放在什么Java包名下
    // 注:如不显式指定,默认包名为:按照应用名称倒序方式进行排序
    
    option java_outer_classname = "Demo";
    // 定义:类名
    // 作用:生成对应.java 文件的类名(不能跟下面message的类名相同)
    // 注:如不显式指定,则默认为把.proto文件名转换为首字母大写来生成
    // 如.proto文件名="my_proto.proto",默认情况下,将使用 "MyProto" 做为类名
    
    option optimize_for = ***;
    // 作用:影响 C++  & java 代码的生成
    // ***参数如下:
    // 1. SPEED (默认)::protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。(最优方式)
    // 2. CODE_SIZE::编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。
      // 特点:采用该方式产生的代码将比SPEED要少很多, 但是效率较低;
      // 使用场景:常用在 包含大量.proto文件 但 不追求效率 的应用中。
    //3.  LITE_RUNTIME::编译器依赖于运行时 核心类库 来生成代码(即采用libprotobuf-lite 替代libprotobuf)。
      // 特点:这种核心类库要比全类库小得多(忽略了 一些描述符及反射 );编译器采用该模式产生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。
      // 应用场景:移动手机平台应用
    
    option cc_generic_services = false;
    option java_generic_services = false;
    option py_generic_services = false;
    // 作用:定义在C++、java、python中,protocol buffer编译器是否应该 基于服务定义 产生 抽象服务代码(2.3.0版本前该值默认 = true)
    // 自2.3.0版本以来,官方认为通过提供 代码生成器插件 来对 RPC实现 更可取,而不是依赖于“抽象”服务
    
    optional repeated int32 samples = 4 [packed=true];
    // 如果该选项在一个整型基本类型上被设置为真,则采用更紧凑的编码方式(不会对数值造成损失)
    // 在2.3.0版本前,解析器将会忽略 非期望的包装值。因此,它不可能在 不破坏现有框架的兼容性上 而 改变压缩格式。
    // 在2.3.0之后,这种改变将是安全的,解析器能够接受上述两种格式。
    
    optional int32 old_field = 6 [deprecated=true];
    // 作用:判断该字段是否已经被弃用
    // 作用同 在java中的注解@Deprecated
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    2.3 消息模型

    • 作用:真正用于描述 数据结构
    // 消息对象用message修饰
    message Person {
    
      required string name = 1;
      required int32 id = 2;
      optional string email = 3;
    
      enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
      }
    
      message PhoneNumber {
        optional PhoneType type = 2 [default = HOME];
      }
    
      repeated PhoneNumber phone = 4;
    }
    
    message AddressBook {
      repeated Person person = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.3.1 消息对象

    • 一个消息对象(Message) = 一个 结构化数据
    • 消息对象用 修饰符 message 修饰
    • 消息对象 含有 字段:消息对象(Message)里的 字段 = 结构化数据 里的成员变量
    message person{
       required int32 id=1;
       optional string userName=2;
       required double check=3;
       repeated string sex=4;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.3.2 字段

    • 消息对象的字段 组成主要是:字段 = 字段修饰符 + 字段类型 +字段名 +标识号
    • required: 格式良好的消息必须恰好具有该字段之一。
    • optional:一个格式良好的消息可以有零个或一个这个字段(但不能超过一个)。
    • repeated:该字段可以在格式良好的消息中重复任意次数(包括零次)。重复值的顺序将被保留。

    由于历史原因,repeated标量数字类型的字段(例如 , , int32)没有尽可能高效地编码。新代码应该使用特殊选项来获得更有效的编码。例如:int64enum[packed = true]

    2.3.3 基本数据类型

    // 基本数据类型
    // message:定义消息模型,java基本实体类
    message person{
       // 必须
       // 字段修饰符 字段类型 字段名 标识符 
       required int32 id=1;
       // 可选
       optional string userName=2;
       // 必须
       required double check=3;
        // 必须
       required string sex=4;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.3.4 枚举类

    • 为字段指定一个 可能取值的字段集合。
    • 枚举类型的定义可在一个消息对象的内部或外部。
    • 都可以在 同一.proto文件 中的任何消息对象里使用。
    • 当枚举类型是在一消息内部定义,希望在 另一个消息中 使用时,需要MessageType.EnumType的语法格式。
    // 基本数据类型,message:定义消息模型
    message person{
       // 必须
       required int32 id=1;
       // 可选
       optional string userName=2;
       required double check=3;
       // 可复用赋值
       repeated string sex=4;
    
       // 性别枚举类
       enum SexType{
          man=1;
          woman=2;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.3.5 消息对象的引用

    • 消息内部使用
    package person;
    
    option java_package="com.shu.proto";
    option java_outer_classname="Person";
    // 编码格式
    option java_string_check_utf8=true;
    // 是否生成hash与equal方法
    option java_generate_equals_and_hash=true;
    
    
    // 基本数据类型,message:定义消息模型
    message person{
       // 必须
       required int32 id=1;
       // 可选
       optional string userName=2;
       required double check=3;
       // 可复用赋值
       repeated string sex=4;
    
       // 性别枚举类
       enum SexType{
          man=1;
          woman=2;
       }
    
        
       // 嵌套消息模型
       message Person_Sex {
          optional SexType type = 2 [default = man];
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 外部消息使用
    package person;
    
    option java_package="com.shu.proto";
    option java_outer_classname="Person";
    // 编码格式
    option java_string_check_utf8=true;
    // 是否生成hash与equal方法
    option java_generate_equals_and_hash=true;
    
    
    // 基本数据类型,message:定义消息模型
    message person{
       // 必须
       required int32 id=1;
       // 可选
       optional string userName=2;
       required double check=3;
       // 可复用赋值
       repeated string sex=4;
    
       // 性别枚举类
       enum SexType{
          man=1;
          woman=2;
       }
    
       // 嵌套消息模型
       message Person_Sex {
          optional SexType type = 2 [default = man];
       }
    }
    
    // 外部消息
    message AddressBook {
       repeated person person = 1;
       // 直接使用了 Person消息类型作为消息字段
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 使用不同的protoc文件
    import "myproject/other_protos.proto"
    // 在A.proto 文件中添加 B.proto文件路径的导入声明
    // ProtocolBuffer编译器 会在 该目录中 查找需要被导入的 .proto文件
    // 如果不提供参数,编译器就在 其调用的目录下 查找
    
    • 1
    • 2
    • 3
    • 4

    2.3.6 标识符

    2.3.7 保留字段

    • 如果您通过完全删除某个字段或将其注释掉来更新消息类型,未来的用户可以在对类型进行自己的更新时重用该字段编号。
    • 如果他们稍后加载相同的旧版本,这可能会导致严重问题.proto,包括数据损坏、隐私错误等。
    • 确保不会发生这种情况的一种方法是指定已删除字段的字段编号(和/或名称,这也可能导致 JSON 序列化问题)为reserved. 如果将来有任何用户尝试使用这些字段标识符,protocol buffer 编译器会抱怨。
    message Foo {
      reserved 2, 15, 9 to 11;
      reserved "foo", "bar";
    }
    
    // 保留最大值
    enum Foo {
      reserved 2, 15, 9 to 11, 40 to max;
      reserved "FOO", "BAR";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.3.8 默认值选项

    • 如果未为可选元素指定默认值,则使用特定于类型的默认值:对于字符串,默认值为空字符串。
    • 对于字节,默认值为空字节字符串。
    • 对于布尔值,默认值为 false。
    • 对于数字类型,默认值为零。
    • 对于枚举,默认值是枚举类型定义中列出的第一个值。这意味着在枚举值列表的开头添加值时必须小心。
    message person{
       required int32 id=1;
       // 设置默认值
       optional string userName=2[default = '测试'];
       required string sex=3;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.3.9 更新消息类型

    • 如果现有的消息类型不再满足您的所有需求 - 例如,您希望消息格式有一个额外的字段 - 但您仍然希望使用以旧格式创建的代码,请不要担心!在不破坏任何现有代码的情况下更新消息类型非常简单。只需记住以下规则:
    • 不要更改任何现有字段的字段编号。
    • 您添加的任何新字段都应该是optional或repeated。这意味着使用“旧”消息格式的代码序列化的任何消息都可以由新生成的代码解析,因为它们不会丢失任何required 元素。您应该为这些元素设置合理的默认值,以便新代码可以与旧代码生成的消息正确交互。类似地,新代码创建的消息可以由旧代码解析:旧二进制文件在解析时会忽略新字段。但是,未知字段不会被丢弃,如果消息稍后被序列化,未知字段也会随之序列化——因此,如果将消息传递给新代码,新字段仍然可用。
    • 只要在更新的消息类型中不再使用字段编号,就可以删除非必填字段。您可能想要重命名该字段,可能添加前缀“OBSOLETE_”,或将字段编号设为 保留,以便您的未来用户.proto不会意外重用该编号。
    • 只要类型和编号保持不变,非必填字段可以转换为扩展名,反之亦然。
    • int32、uint32、int64、uint64和bool都是兼容的——这意味着您可以将字段从其中一种类型更改为另一种类型,而不会破坏向前或向后兼容性。如果从不适合相应类型的线路中解析出一个数字,您将获得与在 C++ 中将该数字强制转换为该类型相同的效果(例如,如果一个 64 位数字被读取为int32,它将被截断为 32 位)。
    • sint32并且sint64彼此兼容,但与其他整数类型_不_ 兼容。
    • string并且bytes只要字节是有效的 UTF-8 就兼容。
    • bytes如果字节包含消息的编码版本,则嵌入消息是兼容的。
    • fixed32与sfixed32和fixed64兼容sfixed64。
    • 对于string、bytes和 消息字段,optional与 兼容 repeated。给定重复字段的序列化数据作为输入,optional如果它是原始类型字段,则期望此字段的客户端将采用最后一个输入值,如果它是消息类型字段,则合并所有输入元素。请注意,这对于数字类型(包括布尔值和枚举)通常不安全**。**数字类型的重复字段可以以 打包optional格式序列化,当需要字段时将无法正确解析。
    • 更改默认值通常是可以的,只要您记住默认值永远不会通过网络发送。因此,如果程序接收到未设置特定字段的消息,则程序将看到在该程序的协议版本中定义的默认值。它不会看到发件人代码中定义的默认值。
    • enum与int32, uint32, int64, 和uint64有线格式兼容(请注意,如果值不合适,将被截断),但请注意,当消息被反序列化时,客户端代码可能会以不同方式处理它们。值得注意的是,enum当消息被反序列化时,无法识别的值将被丢弃,这使得该字段的has…访问器返回 false 并且其 getter 返回定义中列出的第一个值enum,或者如果指定了一个则返回默认值。在重复枚举字段的情况下,任何无法识别的值都会从列表中删除。但是,整数字段将始终保留其值。因此,在将整数升级到 an 时,您需要非常小心,因为enum在线路上接收超出范围的枚举值。
    • 在当前的 Java 和 C++ 实现中,当enum去除无法识别的值时,它们会与其他未知字段一起存储。请注意,如果此数据被序列化然后由识别这些值的客户端重新解析,这可能会导致奇怪的行为。在可选字段的情况下,即使在原始消息反序列化后写入了新值,旧值仍会被识别它的客户端读取。在重复字段的情况下,旧值将出现在任何已识别和新添加的值之后,这意味着将不会保留顺序。
    • 将单个optional值更改为new oneof的成员是安全且二进制兼容的。如果您确定没有代码一次设置多个字段,则将多个optional字段移动到一个新字段 中可能是安全的。oneof将任何字段移动到现有字段oneof中是不安全的。
    • 在 amap和相应的repeated 消息字段之间更改字段是二进制兼容的(请参阅下面的地图,了解消息布局和其他限制)。但是,更改的安全性取决于应用程序:在反序列化和重新序列化消息时,使用repeated字段定义的客户端将产生语义相同的结果;但是,使用map字段定义的客户端可能会重新排序条目并删除具有重复键的条目。

    2.4 RPC服务

    • 定义服务
    package person;
    
    option java_package="com.shu.proto";
    option java_outer_classname="Person";
    // 编码格式
    option java_string_check_utf8=true;
    // 是否生成hash与equal方法
    option java_generate_equals_and_hash=true;
    
    
    // 返回参数
    message person{
       required int32 id=1;
       optional string userName=2;
       repeated string sex=3;
    }
    
    
    // 查询参数
    message SearchRequest {
       repeated int32 id = 1;
    }
    
    
    // 定义RPC服务
    service SearchPersonService {
       rpc Search (SearchRequest) returns (person);
    }
    // // 生成命令 protoc -I=E:\Project\Java\src\Demo --java_out=E:\Project E:\Project\Java\src\Demo\Person.proto
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.5 生成命令

    • Protoco Buffer提供 C++、Java、Python 三种开发语言的 API

    // 在 终端 输入下列命令进行编译
    protoc -I=$SRC_DIR --xxx_out=$DST_DIR   $SRC_DIR/addressbook.proto
    
    // 参数说明
    // 1. $SRC_DIR:指定需要编译的.proto文件目录 (如没有提供则使用当前目录)
    // 2. --xxx_out:xxx根据需要生成代码的类型进行设置
    // 对于 Java ,xxx =  java ,即 -- java_out
    // 对于 C++ ,xxx =  cpp ,即 --cpp_out
    // 对于 Python,xxx =  python,即 --python_out
    
    // 3. $DST_DIR :编译后代码生成的目录 (通常设置与$SRC_DIR相同)
    // 4. 最后的路径参数:需要编译的.proto 文件的具体路径
    
    // 编译通过后,Protoco Buffer会根据不同平台生成对应的代码文件
    // eg:protoc -I=E:\Project\Java\src\Demo --java_out=E:\Project E:\Project\Java\src\Demo\Person.proto
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    三 基本使用

    3.1 依赖导入

    • 注意这里的依赖与安装的版本一致
            <dependency>
                <groupId>com.google.protobufgroupId>
                <artifactId>protobuf-javaartifactId>
                <version>2.5.0version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2 消息对象类介绍

    • 通过.proto文件 转换的 Java源代码 = Protocol Buffer 类 + 消息对象类(含Builder内部类)

    3.2.1 Message类

    • 基本方法
    <-- 方式1:直接序列化和反序列化 消息 -->
    protocolBuffer.toByteArray();
    // 序列化消息 并 返回一个包含它的原始字节的字节数组
    protocolBuffer.parseFrom(byte[] data);
    // 从一个字节数组 反序列化(解析) 消息
    
    <-- 方式2:通过输入/ 输出流(如网络输出流) 序列化和反序列化消息 -->
    protocolBuffer.writeTo(OutputStream output);
    output.toByteArray();
    // 将消息写入 输出流 ,然后再 序列化消息 
    
    protocolBuffer.parseFrom(InputStream input);
    
    // 基本的get与set方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    package com.example.thrift.proto;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.util.Arrays;
    
    /**
     * @Author shu
     * @Date: 2022/03/01/ 13:39
     * @Description proto方法
     **/
    public class PersonTest {
        public static void main(String[] args) {
            // 通过 消息类的内部类Builder类 构造 消息类的消息构造器
            Person.person.Builder builder = Person.person.newBuilder();
            // 设置值
            builder.setId(1);
            builder.setUserName("xiaoMing");
            // 通过 消息构造器 创建 消息类 对象
            Person.person person = builder.build();
    
            /**
             * 直接序列化
             */
            // 序列化
            byte[] array = person.toByteArray();
            // 打印日志
            System.out.println(Arrays.toString(array));
            // 反序列化消息
            try {
    
                Person.person parse = Person.person.parseFrom(array);
                // 当接收到字节数组byte[] 反序列化为 person消息类对象
                System.out.println(parse.getId());
                System.out.println(parse.getUserName());
                // 输出反序列化后的消息
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
    
            /**
             * 流序列化
             */
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            try {
                person.writeTo(output);
                // 将消息序列化 并写入 输出流(此处用 ByteArrayOutputStream 代替)
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
            byte[] byteArray = output.toByteArray();
            // 通过 输出流 转化成二进制字节流
            // b. 反序列化
            ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
            // 通过 输入流 接收消息流(此处用 ByteArrayInputStream 代替)
            try {
    
                Person.person parse = Person.person.parseFrom(input);
                // 通过输入流 反序列化 消息
                System.out.println(parse.getId());
                System.out.println(parse.getUserName());
                // 输出消息
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    3.2.2 Builder类

    • 创建 消息构造器 & 设置/ 获取消息对象的字段值 & 创建 消息类 实例
    • 属于 消息对象类 的内部类
      // 通过 消息类的内部类Builder类 构造 消息类的消息构造器
       Person.person.Builder builder = Person.person.newBuilder();
      // 通过 消息构造器 创建 消息类 对象
        Person.person person = builder.build();
    
    public Builder isInitialized() 
    // 检查所有 required 字段 是否都已经被设置
    
    public Builder toString() :
    // 返回一个人类可读的消息表示(用于调试)
    
    public Builder mergeFrom(Message other)
    // 将 其他内容 合并到这个消息中,覆写单数的字段,附接重复的。
    
    public Builder clear()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.3 使用步骤

    • **步骤1:**通过 消息类的内部类Builder类 构造 消息构造器
    • **步骤2:**通过 消息构造器 设置 消息字段的值
    • **步骤3:**通过 消息构造器 创建 消息类 对象
    • 步骤4:序列化 / 反序列化 消息
    package com.example.thrift.proto;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.util.Arrays;
    
    /**
     * @Author shu
     * @Date: 2022/03/01/ 13:39
     * @Description proto方法
     **/
    public class PersonTest {
        public static void main(String[] args) {
            // 通过 消息类的内部类Builder类 构造 消息类的消息构造器
            Person.person.Builder builder = Person.person.newBuilder();
            // 设置值
            builder.setId(1);
            builder.setUserName("xiaoMing");
            // 通过 消息构造器 创建 消息类 对象
            Person.person person = builder.build();
    
            /**
             * 直接序列化
             */
            // 序列化
            byte[] array = person.toByteArray();
            // 打印日志
            System.out.println(Arrays.toString(array));
            // 反序列化消息
            try {
    
                Person.person parse = Person.person.parseFrom(array);
                // 当接收到字节数组byte[] 反序列化为 person消息类对象
                System.out.println(parse.getId());
                System.out.println(parse.getUserName());
                // 输出反序列化后的消息
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
    
            /**
             * 流序列化
             */
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            try {
                person.writeTo(output);
                // 将消息序列化 并写入 输出流(此处用 ByteArrayOutputStream 代替)
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
            byte[] byteArray = output.toByteArray();
            // 通过 输出流 转化成二进制字节流
            // b. 反序列化
            ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
            // 通过 输入流 接收消息流(此处用 ByteArrayInputStream 代替)
            try {
    
                Person.person parse = Person.person.parseFrom(input);
                // 通过输入流 反序列化 消息
                System.out.println(parse.getId());
                System.out.println(parse.getUserName());
                // 输出消息
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    3.4 基本方法

    Message接口定义了允许您检查、操作、读取或写入整个消息的方法。除了这些方法,Foo该类还定义了以下静态方法:

    • static Foo getDefaultInstance(): 返回 的_单例_实例Foo。此实例的内容与您调用时获得的内容相同Foo.newBuilder().build()(因此所有单数字段均未设置,所有重复字段均为空)。newBuilderForType()请注意,可以通过调用其方法 将消息的默认实例用作工厂。
    • static Descriptor getDescriptor():返回类型的描述符。这包含有关类型的信息,包括它具有哪些字段以及它们的类型。这可以与反射方法一起使用Message,例如getField().
    • static Foo parseFrom(…):解析Foo来自给定源的类型消息并返回它。接口中的每个变体都有一个parseFrom方法对应。请注意,从不抛出mergeFrom()Message.BuilderparseFrom()UninitializedMessageException; InvalidProtocolBufferException如果解析的消息缺少必填字段,它会抛出。这使它与 call 略有不同Foo.newBuilder().mergeFrom(…).build()。
    • static Parser parser(): 返回一个实例Parser,它实现了各种parseFrom()方法。
    • Foo.Builder newBuilder():创建一个新的构建器(如下所述)。
    • Foo.Builder newBuilder(Foo prototype):创建一个新的构建器,将所有字段初始化为它们在其中的相同值prototype。由于嵌入的消息和字符串对象是不可变的,它们在原始和副本之间共享。
  • 相关阅读:
    俄罗斯方块c语言
    02- 数据结构与算法 - 最长回文子串(动态规划/中心扩展算法/Manacher 算法)
    开源图编辑库 NebulaGraph VEditor 的设计思路分享
    数据结构 - 红黑树
    类中报错 xxx does not name a type可能因为类中修改了对象
    【深度学习实践】HaGRID,YOLOv5,手势识别项目,目标检测实践项目
    python调用外部exe程序 等待程序执行完后后 往下运行 直接往下运行
    Spring 源码阅读 13:执行 BeanFactoryPostProcessor 中的处理方法
    MinIO (一)安装并生成windows服务
    【JMX】JMX远程监控JVM参数配置
  • 原文地址:https://blog.csdn.net/weixin_44451022/article/details/125880648