• protobuf使用详解


    一、protobuf简介

    1、什么是 protobuf

    Protocal Buffers(简称protobuf)是谷歌的一项技术,用于结构化的数据序列化、反序列化。

    官方解释:Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法。可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

    你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

    2、为什么使用protobuf

    由于 protobuf是跨语言的,所以用不同的语言序列化对象后,生成一段字节码,之后可以其他任何语言反序列化并自用,大大方便了跨语言的通讯,同时也提高了效率。

    需要注意: protobuf生成的是字节码,可读性相比略差一点。

    二、protobuf数据类型

    创建 FileName.proto文件,后缀名称必须是.proto。一般一个文件就代表一个 proto对象。在文件中定义 proto 对象的属性。通过 .proto文件可以生成不同语言的类,用于结构化的数据序列化、反序列化。

    protobuf官方文档:https://protobuf.dev/programming-guides/proto3/

    定义一个 proto 对象的属性,基本格式如下:

    字段标签(可选) 字段类型 字段名称 字段标识符 字段默认值(可选)

    关于字段编号(标识符),是字段中唯一且必须的,以 1开始,不能重复,不能跳值,这个是和编译有关系的。

    1、基本数据类型

    常见基本数据类型:

    在这里插入图片描述

    系统默认值:

    • string:默认为空字符串
    • byte:默认值为空字节
    • bool:默认为false
    • 数值:默认为0
    • enum:默认为第一个元素

    示例如下:

    syntax = "proto3";
    
    //创建一个 SearchRequest 对象
    message SearchRequest {
      string query = 1;
      int32 page_number = 2;
      int32 results_per_page = 3;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、复杂数据类型

    下面通过 Java数据类型来理解定义的 proto属性。并引入 protobuf-java依赖:

           
            <dependency>
                <groupId>com.google.protobufgroupId>
                <artifactId>protobuf-javaartifactId>
                <version>3.19.1version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.1 集合List字段

    Java String、Integer List 在 protobuf 的定义。

    message User{
      //list Int
      repeated int32 intList = 1;
      //list String
      repeated string strList = 2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.2 Map字段

    Java String、Integer Map 在 protobuf 的定义。

    message User{
      // 定义简单的 Map string
      map<string, int32> intMap = 7;
      // 定义复杂的 Map 对象
      map<string, string> stringMap = 8;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.3 对象字段

    Java 对象 List 在 protobuf 的定义。

    message User{
      //list 对象
      repeated Role roleList = 6;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.4 Map对象值字段

    Java 对象 Map 在 protobuf 的定义。

    message User{
      // 定义复杂的 Map 对象
      map<string, MapVauleObject> mapObject = 8;
    }
    
    
    // 定义 Map 的 value 对象
    message MapVauleObject {
      string code = 1;
      string name = 2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.5 嵌套对象字段

    Java 实体类中使用另一个实体类作为字段在 protobuf 的定义。

    message User{
      // 对象
      NickName nickName = 4;
    }
    
    // 定义一个新的Name对象
    message NickName {
      string nickName = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    三、示例实战

    1、基本数据类型

    (1).proto文件

    syntax = "proto3";
    
    //生成 proto 文件所在包路径(一般不指定, 生成java类之后人为手动加即可)
    //package com.example.xxx.model;
    
    //生成 proto 文件所在 java包路径(一般不指定,因为生成的java_outer_classname类中使用到它会使用全限定名)
    //option java_package = "com.example.xxx.model";
    
    //生成 proto java文件名(一般指定,文件名+自定义。如果不指定,默认时文件名+OuterClass)
    option java_outer_classname = "UserProtoBuf";
    
    message User {
    
      int32 age = 1;
      int64 timestamp = 2;
      bool enabled = 3;
      float height = 4;
      double weight = 5;
      string userName = 6;
      string Full_Address = 7;
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    生成 Java类。

    注意:proto没有指定 package xxx; 所以,我们将 java类放到目标包下面时,记得手动导包。

    在这里插入图片描述
    (2)测试类

    protobuf数据(字节数组)序列化、反序列化。

    public class UserTest {
    
        public static void main(String[] args) throws Exception {
    
            // 将数据序列化
            byte[] byteData = getClientPush();
            System.out.println("获取到字节数据:byteData长度="+ byteData.length);
            System.out.println("===========");
    
            /**
             * 接收数据反序列化:将字节数据转化为对象数据。
             */
            UserProtoBuf.User user = UserProtoBuf.User.parseFrom(byteData);
            System.out.println("user=" + user);
            System.out.println("UserName=" + user.getUserName());
            System.out.println("Timestamp=" + user.getTimestamp());
            System.out.println("Height=" + user.getHeight());
        }
    
        /**
         * 模拟发送方,将数据序列化后发送
         * @return
         */
        private static byte[] getClientPush() {
            // 按照定义的数据结构,创建一个对象。
            UserProtoBuf.User.Builder user = UserProtoBuf.User.newBuilder();
            user.setAge(18);
            user.setTimestamp(System.currentTimeMillis());
            user.setEnabled(true);
            //user.setHeight(1.88F);
            user.setWeight(66.76D);
            user.setUserName("赵云");
            user.setFullAddress("王者-打野");
    
            /**
             * 发送数据序列化:将对象数据转化为字节数据输出
             */
            UserProtoBuf.User userBuild = user.build();
            byte[] bytes = userBuild.toByteArray();
            return bytes;
        }
    
    }
    
    • 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

    在这里插入图片描述

    2、集合/Map类型

    (1).proto文件

    syntax = "proto3";
    
    option java_outer_classname = "UserListMapProtoBuf";
    
    message UserListMap {
    
      string userName = 1;
      //list Int
      repeated int32 intList = 2;
      //list String
      repeated string strList = 3;
    
      // 定义Map对象
      map<string, int32> intMap = 4;
      // 定义Map对象
      map<string, string> stringMap = 5;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (2)测试类

    public class UserListMapTest {
    
        public static void main(String[] args) throws Exception {
    
            // 将数据序列化
            byte[] byteData = getClientPush();
            System.out.println("获取到字节数据:byteData长度="+ byteData.length);
            System.out.println("===========");
    
            /**
             * 接收数据反序列化:将字节数据转化为对象数据。
             */
            UserListMapProtoBuf.UserListMap userListMap = UserListMapProtoBuf.UserListMap.parseFrom(byteData);
            System.out.println("UserListMap=" + userListMap);
            System.out.println("UserName=" + userListMap.getUserName());
            System.out.println("IntList=" + userListMap.getIntListList());
            System.out.println("StrList=" + userListMap.getStrListList());
            System.out.println("IntMap=" + userListMap.getIntMapMap());
            System.out.println("StringMap=" + userListMap.getStringMapMap());
        }
    
        /**
         * 模拟发送方,将数据序列化后发送
         * @return
         */
        private static byte[] getClientPush() {
            // 按照定义的数据结构,创建一个对象。
            UserListMapProtoBuf.UserListMap.Builder userListMap = UserListMapProtoBuf.UserListMap.newBuilder();
            userListMap.setUserName("赵云");
    
            List<Integer> intList = new ArrayList<>();
            List<String> strList = new ArrayList<>();
            intList.add(50);
            intList.add(51);
            strList.add("字符串1");
            strList.add("字符串2");
            userListMap.addAllIntList(intList);
            userListMap.addAllStrList(strList);
    
            Map<String, String> strMap = new HashMap<>();
            strMap.put("str-k1", "v1");
            strMap.put("str-k2", "v2");
            userListMap.putIntMap("integer-k1", 60);
            userListMap.putIntMap("integer-k2", 61);
            userListMap.putAllStringMap(strMap);
    
    
            /**
             * 发送数据序列化:将对象数据转化为字节数据输出
             */
            UserListMapProtoBuf.UserListMap userListBuild = userListMap.build();
            byte[] bytes = userListBuild.toByteArray();
            return bytes;
        }
    
    }
    
    • 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

    在这里插入图片描述

    3、嵌套对象类型

    (1).proto文件

    syntax = "proto3";
    
    option java_outer_classname = "DemoObjectProtoBuf";
    
    message DemoObject {
    
      string userName = 1;
      //list InnerObject
      repeated InnerObject innerObjectList = 2;
    
      // 定义Map对象
      map<string, InnerObject> innerObjectMap = 3;
    
    }
    
    // 定义 InnerObject2对象
    message InnerObject {
      string name = 1;
      int32 age = 2;
      string code = 3;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    (2)测试类

    public class DemoObjectTest {
    
        public static void main(String[] args) throws Exception {
    
            // 将数据序列化
            byte[] byteData = getClientPush();
            System.out.println("获取到字节数据:byteData长度=" + byteData.length);
            System.out.println("===========");
    
            /**
             * 接收数据反序列化:将字节数据转化为对象数据。
             */
            DemoObjectProtoBuf.DemoObject demoObject = DemoObjectProtoBuf.DemoObject.parseFrom(byteData);
            //System.out.println("DemoObject=" + demoObject);
            System.out.println("UserName=" + demoObject.getUserName());
            List<DemoObjectProtoBuf.InnerObject> innerObjectList = demoObject.getInnerObjectListList();
            for (DemoObjectProtoBuf.InnerObject innerObject : innerObjectList) {
                System.out.println("innerObject=" + innerObject);
                System.out.println("Name=" + innerObject.getName());
            }
            Map<String, DemoObjectProtoBuf.InnerObject> innerObjectMap = demoObject.getInnerObjectMapMap();
            innerObjectMap.forEach((k, v) -> {
                System.out.println("k=" + k);
                System.out.println("v=" + v);
            });
    
        }
    
        /**
         * 模拟发送方,将数据序列化后发送
         *
         * @return
         */
        private static byte[] getClientPush() {
            DemoObjectProtoBuf.InnerObject innerObject1 = DemoObjectProtoBuf.InnerObject.newBuilder()
                    .setName("in 赵子龙2")
                    .setAge(18)
                    .setCode("code1").build();
            DemoObjectProtoBuf.InnerObject innerObject2 = DemoObjectProtoBuf.InnerObject.newBuilder()
                    .setName("in 赵子龙2")
                    .setAge(19)
                    .setCode("code2").build();
            List<DemoObjectProtoBuf.InnerObject> innerObjList = new ArrayList<>();
            innerObjList.add(innerObject1);
            innerObjList.add(innerObject2);
            Map<String, DemoObjectProtoBuf.InnerObject> innerObjMap = new HashMap<>();
            innerObjMap.put("k1", innerObject1);
            innerObjMap.put("k2", innerObject2);
    
            // 按照定义的数据结构,创建一个对象。
            DemoObjectProtoBuf.DemoObject.Builder demoObject = DemoObjectProtoBuf.DemoObject.newBuilder();
            demoObject.setUserName("赵云");
            demoObject.addAllInnerObjectList(innerObjList);
            demoObject.putAllInnerObjectMap(innerObjMap);
    
            /**
             * 发送数据序列化:将对象数据转化为字节数据输出
             */
            DemoObjectProtoBuf.DemoObject demoObjectBuild = demoObject.build();
            byte[] bytes = demoObjectBuild.toByteArray();
            return bytes;
        }
    
    }
    
    • 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

    在这里插入图片描述

    4、引入外部 proto对象类型

    外部 proto文件,使用上面的 User.proto。

    (1).proto文件

    syntax = "proto3";
    
    option java_outer_classname = "Demo2ObjectProtoBuf";
    
    // 引入外部的 proto 对象
    import "User.proto";
    
    message Demo2Object {
    
      string userName = 1; // default = "张三"
    
      //list Int
      repeated int32 intList = 2;
    
      //list 对象(User为引入的外部 proto文件)
      repeated User userList = 3;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (2)测试类

    public class DemoObject2Test {
    
        public static void main(String[] args) throws Exception {
    
            // 将数据序列化
            byte[] byteData = getClientPush();
            System.out.println("获取到字节数据:byteData长度=" + byteData.length);
            System.out.println("===========");
    
            /**
             * 接收数据反序列化:将字节数据转化为对象数据。
             */
            Demo2ObjectProtoBuf.Demo2Object demo2Object = Demo2ObjectProtoBuf.Demo2Object.parseFrom(byteData);
            //System.out.println("Demo2Object=" + demo2Object);
            System.out.println("UserName=" + demo2Object.getUserName());
            List<UserProtoBuf.User> userList = demo2Object.getUserListList();
            for (UserProtoBuf.User user : userList) {
                System.out.println("user=" + user);
            }
    
        }
    
        /**
         * 模拟发送方,将数据序列化后发送
         *
         * @return
         */
        private static byte[] getClientPush() {
            UserProtoBuf.User user = UserProtoBuf.User.newBuilder()
            .setAge(18)
            .setTimestamp(System.currentTimeMillis())
            .setEnabled(true)
            //.setHeight(1.88F)
            .setWeight(66.76D)
            .setUserName("赵云")
            .setFullAddress("王者-打野").build();
            List<UserProtoBuf.User> userList = new ArrayList<>();
            userList.add(user);
            userList.add(user);
    
            // 按照定义的数据结构,创建一个对象。
            Demo2ObjectProtoBuf.Demo2Object.Builder demo2Object = Demo2ObjectProtoBuf.Demo2Object.newBuilder();
            demo2Object.setUserName("赵云");
            demo2Object.addAllUserList(userList);
    
            /**
             * 发送数据序列化:将对象数据转化为字节数据输出
             */
            Demo2ObjectProtoBuf.Demo2Object demo2ObjectBuild = demo2Object.build();
            byte[] bytes = demo2ObjectBuild.toByteArray();
            return bytes;
        }
    
    }
    
    • 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

    – 求知若饥,虚心若愚。

  • 相关阅读:
    从一篇AMA揭幕单慢雾安全技术
    使用第三方账号认证(一):钉钉扫码登录
    基于MTCNN和FaceNet的实时人脸检测识别系统
    FFmpeg 命令:从入门到精通 | ffmpeg 命令视频录制
    c++(26) 输入输出流、文件操作
    如何在Java中轻松使用工厂设计模式
    从 C 到 C++ 编程 — 面向对象编程
    Direct3D融合技术
    利用网络管理解决方案简化网络运维
    Strimzi Kafka Bridge(桥接)实战之二:生产和发送消息
  • 原文地址:https://blog.csdn.net/qq_42402854/article/details/134066566