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:默认为第一个元素

示例如下:

java 复制代码
syntax = "proto3";

//创建一个 SearchRequest 对象
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 results_per_page = 3;
}

3、复杂数据类型

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

xml 复制代码
       <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.19.1</version>
        </dependency>

3.1 集合List字段

Java String、Integer List 在 protobuf 的定义。

java 复制代码
message User{
  //list Int
  repeated int32 intList = 1;
  //list String
  repeated string strList = 2;
}

3.2 Map字段

Java String、Integer Map 在 protobuf 的定义。

java 复制代码
message User{
  // 定义简单的 Map string
  map<string, int32> intMap = 7;
  // 定义复杂的 Map 对象
  map<string, string> stringMap = 8;
}

3.3 对象字段

Java 对象 List 在 protobuf 的定义。

java 复制代码
message User{
  //list 对象
  repeated Role roleList = 6;
}

3.4 Map对象值字段

Java 对象 Map 在 protobuf 的定义。

java 复制代码
message User{
  // 定义复杂的 Map 对象
  map<string, MapVauleObject> mapObject = 8;
}


// 定义 Map 的 value 对象
message MapVauleObject {
  string code = 1;
  string name = 2;
}

3.5 嵌套对象字段

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

java 复制代码
message User{
  // 对象
  NickName nickName = 4;
}

// 定义一个新的Name对象
message NickName {
  string nickName = 1;
}

三、示例实战

1、基本数据类型

(1).proto文件

java 复制代码
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;
  
}

生成 Java类。

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


(2)测试类

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

java 复制代码
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;
    }

}

2、集合/Map类型

(1).proto文件

java 复制代码
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对象<string, int32>
  map<string, int32> intMap = 4;
  // 定义Map对象<string, string>
  map<string, string> stringMap = 5;

}

(2)测试类

java 复制代码
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;
    }

}

3、嵌套对象类型

(1).proto文件

java 复制代码
syntax = "proto3";

option java_outer_classname = "DemoObjectProtoBuf";

message DemoObject {

  string userName = 1;
  //list InnerObject
  repeated InnerObject innerObjectList = 2;

  // 定义Map对象<string, InnerObject>
  map<string, InnerObject> innerObjectMap = 3;

}

// 定义 InnerObject2对象
message InnerObject {
  string name = 1;
  int32 age = 2;
  string code = 3;
}

(2)测试类

java 复制代码
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;
    }

}

4、引入外部 proto对象类型

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

(1).proto文件

java 复制代码
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;

}

(2)测试类

java 复制代码
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;
    }

}

-- 求知若饥,虚心若愚。