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;
}

3、复杂数据类型

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

       <!-- 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 的定义。

message User{
  //list Int
  repeated int32 intList = 1;
  //list String
  repeated string strList = 2;
}

3.2 Map字段

Java String、Integer Map 在 protobuf 的定义。

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

3.3 对象字段

Java 对象 List 在 protobuf 的定义。

message User{
  //list 对象
  repeated Role roleList = 6;
}

3.4 Map对象值字段

Java 对象 Map 在 protobuf 的定义。

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


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

3.5 嵌套对象字段

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

message User{
  // 对象
  NickName nickName = 4;
}

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

三、示例实战

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;
  
}

生成 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;
    }

}

在这里插入图片描述

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

}

(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;
    }

}

在这里插入图片描述

3、嵌套对象类型

(1).proto文件

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)测试类

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文件

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)测试类

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;
    }

}

– 求知若饥,虚心若愚。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
ProtobufProtocol Buffers)是一种轻量级的数据序列化格式,由Google开发。它可以用于结构化数据的序列化,用于数据通信、持久化和配置文件等场景。下面是使用protobuf的一般步骤: 1. 定义消息类型:使用protobuf语言定义文件(.proto)来描述数据结构和消息类型。你可以定义消息字段的名称、类型和规则等。 2. 编写.proto文件:在.proto文件中定义消息类型、字段和其他相关信息。例如,你可以定义消息的名称、字段的名称和类型、字段的规则(如必填、可选或重复)等。 3. 编译.proto文件:使用protobuf编译器将.proto文件编译为你所选编程语言的源代码。protobuf支持多种编程语言,如C++、Java、Python等。编译后会生成对应语言的源代码文件,其中包含与消息类型相关的类或结构体。 4. 在代码中使用protobuf:在你的代码中引入生成的源代码文件,并使用其中定义的类或结构体。你可以根据需要创建、修改和序列化protobuf消息,以及将其转换为二进制格式或其他格式。 5. 序列化和反序列化:使用protobuf库提供的方法将protobuf消息序列化为二进制格式,或者将二进制数据反序列化为protobuf消息。这样可以实现消息的传输和存储。 总结来说,使用protobuf可以实现跨语言、高效的数据序列化和反序列化,简化了数据传输和存储的过程。通过定义和编译.proto文件,并在代码中使用生成的源代码文件,你可以方便地使用protobuf进行数据处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值