与传统序列化比,PB更快更节省空间

为何选择PB

在这里插入图片描述
在网络传输和存储数据的时候比传统的 JSON 效果更好

PB安装

GitHub

Windows

  1. 下载
    在这里插入图片描述
  2. 配置环境变量
    在这里插入图片描述
  3. 验证
    在这里插入图片描述

Mac未完待续

后续补充Mac安装方式

语法

在这里插入图片描述
使用过程很简单,并没有太多的语法关注点。我们只需要掌握重要的一些方法即可

创建的 .proto 文件一定要全部小写,中间用 “_” 链接【lower_snake_case.proto】

// 字段规则,说明是 proto3 语法
syntax = "proto3";
// 包名
package contact;

// 开启多文件
option java_multiple_files = true;
// 生成的 .java 文件的包路径
option java_package = "entityPB";
// 生成的 .proto 包装类的 .java 文件的类名
option java_outer_classname = "ContactsProto";

// 需要用到 any 数据类型就需要导入包【protoc-23.1-win64\include\google\protobuf】
import "google/protobuf/any.proto";

// message 消息类型命名规范:使用驼峰命名,首字母大写
message PeopleInfoPB{
  /*
  字段定义格式为:字段类型 字段名 = 字段唯⼀编号
    字段名称命名规范:全小写字⺟,多个字⺟之间⽤ _ 连接。
    字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。
    字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。
   */
  string              name   = 1;// 姓名
  int32               age    = 2;// 年龄
  message Phone{
    string    number = 1;// 电话号码
    /*
    枚举
    同一文件下枚举类型不能出现相同的字段
     */
    enum PhoneType {
      MP  = 0;// 移动电话
      TEL = 1;// 固定电话
    }
    PhoneType type   = 2;// 电话类型
  }
  // repeated 说明 phone 这个数据是一个可重复的,也就是数组的形式
  repeated Phone      phone  = 3;
  /*
  oneof:如果消息中有很多字段,但是只会用到一个字段。后续设置的 wechat 会将 QQ 清空
   */
  oneof other_contact{// 其它联系方式
    string qq     = 4;
    string wechat = 5;
  }
  /*
  any 类型可以理解为泛型类型
  可以用 repeated 修饰
   */
  google.protobuf.Any data   = 6;// 存放联系地址
  /*
  map 不能用 repeated 修饰
  key:除了 float,bytes 以外任意的类型
  value:任意类型
   */
  map<string, string> remark = 7;// 备注
}

message Address{
  string home_address = 1;// 家庭地址
  string unit_address = 2;// 公司地址
}

message Contacts {
  repeated PeopleInfoPB contacts = 1;// 通讯录
}

.protoType标量类型Notesjava
int32使⽤变⻓编码[1]。负数的编码效率较低,若字段可能为负值,应使⽤ sint32 代替int
int64使⽤变⻓编码[1]。负数的编码效率较低,若字段可能为负值,应使⽤ sint64 代替int
uint32使⽤变⻓编码[1]int
uint64使⽤变⻓编码[1]int
sint32使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的 int32 类型int
sint64使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的 int64 类型long
fixed32定⻓ 4 字节。若值常⼤于2^28 则会⽐ uint32 更⾼效int
fixed64定⻓ 8 字节。若值常⼤于2^56 则会⽐ uint64 更⾼效long
sfixed32定⻓ 4 字节int
sfixed64定⻓ 8 字节long
floatfloat
doubledouble
bytes可包含任意的字节序列但⻓度不能超过 2^32ByteString
boolboolean
string包含 UTF-8 和 ASCII 编码的字符串,⻓度不能超过2^32string
类型
string空字符串
bytes空字节
boolfalse
int0
enum枚举默认值是第一个枚举值,必须为0
消息字段未设置该字段,默认值依赖于具体的语言
repeated修饰默认是一个列表【addXXX方法】
对于消息字段,oneof字段和any字段都有has方法来检测当前字段是否被设置值

命令行编译

# protoc -I 搜索路径 --java_out=输出的java文件路径 .proto文件
protoc -I BasicGrammar/src/main/proto/start --java_out=BasicGrammar/src/main/java contacts.proto

这种每次不方便,因此衍生出插件编译

Maven插件编译

要选择版本对应的。我的 protocol buff 是 3.23.1,那么插件版本对应的也应该是 3.23.1

maven-repository仓库在这里插入图片描述

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java 版本对应【23.1】-->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.23.1</version>
</dependency>

<plugin>
    <!-- https://mvnrepository.com/artifact/org.xolstice.maven.plugins/protobuf-maven-plugin -->
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <!--本地安装的protoc.exe运行路径-->
        <protocExecutable>D:\Documents\Tools\protoc-23.1-win64\bin\protoc.exe</protocExecutable>
        <!--protoc文件放置的目录,默认为/src/main/proto-->
        <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
        <!--生成文件的目录,默认生成到target/generated-sources/protobuf/下-->
        <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
        <!--是否清空目标目录,默认值为true。这个最好设置为false,以免误删项目文件-->
        <clearOutputDirectory>false</clearOutputDirectory>
    </configuration>
</plugin>

每次双击插件即可完成编译指令
在这里插入图片描述

UDP通信的例子

网络数据传输中需要对数据进行序列化和反序列化
.客户端protp文件代码

syntax = "proto3";
package client_internet;

option java_multiple_files = true;
option java_package = "internet.client";
option java_outer_classname = "ContactsProtos";

message Request{
  string         name  = 1;
  int32          age   = 2;
  message Phone{
    string number = 1;
  }
  repeated Phone phone = 3;
}

message Response{
  string uid = 1;
}

客户端Java代码

package internet.client;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ContactsClient {
    private static void clientStart(String ip, int port) throws IOException {
        // 1.创建客户端 socket
        DatagramSocket socket = new DatagramSocket();
        // 2.序列化发送给服务端的数据
        Request req = Request.newBuilder().setName("zhang san").setAge(20).addPhone(Request.Phone.newBuilder().setNumber("177-9824-4450")).build();
        byte[] reqData = req.toByteArray();
        // 3.给服务端发送数据
        DatagramPacket reqPacket = new DatagramPacket(reqData, reqData.length, InetAddress.getByName(ip), port);
        socket.send(reqPacket);
        // 4.接收服务端数据
        DatagramPacket respPacket = new DatagramPacket(new byte[8192], 8192);
        socket.receive(respPacket);
        // 5.反序列化接收到的服务端数据
        int len = respPacket.getLength();
        byte[] respData = respPacket.getData();
        byte[] respNewData = new byte[len];
        System.arraycopy(respData, 0, respNewData, 0, len);
        Response response = Response.parseFrom(respNewData);
        System.out.printf("接收服务端数据:" + response.toString());
        socket.close();
    }

    public static void main(String[] args) throws IOException {
        String ip = "127.0.0.1";
        int port = 9090;
        clientStart(ip, port);
    }
}

在这里插入图片描述

服务端 .proto 代码

syntax = "proto3";
package service_internet;

option java_multiple_files = true;
option java_package = "internet.service";
option java_outer_classname = "ContactsProtos";

message Request{
  string         name  = 1;
  int32          age   = 2;
  message Phone{
    string number = 1;
  }
  repeated Phone phone = 3;
}

message Response{
  string uid = 1;
}

服务端代码

package internet.service;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;

// 服务端反序列化
public class ContactsService {
    private static void startService(int port) throws IOException {
        // 1.创建服务端 socket
        DatagramSocket socket = new DatagramSocket(port);
        while (true) {
            System.out.println("等待客户端发送数据...");
            // 2.接收客户端数据
            DatagramPacket reqPacket = new DatagramPacket(new byte[8192], 8192);
            socket.receive(reqPacket);
            // 3.反序列化接收到的客户端数据
            byte[] reqData = reqPacket.getData();
            int len = reqPacket.getLength();
            byte[] reqNewData = new byte[len];
            System.arraycopy(reqData, 0, reqNewData, 0, len);
            Request request = Request.parseFrom(reqNewData);
//            System.out.println("接收客户端端数据:" + new String(request.toByteArray(), StandardCharsets.UTF_8));
            System.out.println("接收客户端端数据:" + request.toString());
            // 4.序列化发送给客户端的数据
            Response response = Response.newBuilder().setUid("200").build();
            byte[] respData = response.toByteArray();
            // 5.给客户端发送数据
            DatagramPacket respPacket = new DatagramPacket(respData, 0, respData.length, reqPacket.getSocketAddress());
            socket.send(respPacket);
        }
    }

    public static void main(String[] args) throws IOException {
        int port = 9090;
        startService(port);
    }
}

在这里插入图片描述

3大序列化方法对比

PB.proto 代码

// 字段规则,说明是 proto3 语法
syntax = "proto3";
// 包名
package contact;

// 开启多文件
option java_multiple_files = true;
// 生成的 .java 文件的包路径
option java_package = "entityPB";
// 生成的 .proto 包装类的 .java 文件的类名
option java_outer_classname = "ContactsProto";

// 需要用到 any 数据类型就需要导入包【protoc-23.1-win64\include\google\protobuf】
import "google/protobuf/any.proto";

// message 消息类型命名规范:使用驼峰命名,首字母大写
message PeopleInfoPB{
  /*
  字段定义格式为:字段类型 字段名 = 字段唯⼀编号
    字段名称命名规范:全小写字⺟,多个字⺟之间⽤ _ 连接。
    字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。
    字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。
   */
  string              name   = 1;// 姓名
  int32               age    = 2;// 年龄
  message Phone{
    string    number = 1;// 电话号码
    /*
    枚举
    同一文件下枚举类型不能出现相同的字段
     */
    enum PhoneType {
      MP  = 0;// 移动电话
      TEL = 1;// 固定电话
    }
    PhoneType type   = 2;// 电话类型
  }
  // repeated 说明 phone 这个数据是一个可重复的,也就是数组的形式
  repeated Phone      phone  = 3;
  /*
  oneof:如果消息中有很多字段,但是只会用到一个字段。后续设置的 wechat 会将 QQ 清空
   */
  oneof other_contact{// 其它联系方式
    string qq     = 4;
    string wechat = 5;
  }
  /*
  any 类型可以理解为泛型类型
  可以用 repeated 修饰
   */
  google.protobuf.Any data   = 6;// 存放联系地址
  /*
  map 不能用 repeated 修饰
  key:除了 float,bytes 以外任意的类型
  value:任意类型
   */
  map<string, string> remark = 7;// 备注
}

message Address{
  string home_address = 1;// 家庭地址
  string unit_address = 2;// 公司地址
}

message Contacts {
  repeated PeopleInfoPB contacts = 1;// 通讯录
}

普通的Java实体类

package entity;

import java.util.HashMap;
import java.util.List;

public class PeopleInfoEntity {
    private String name;
    private int age;
    private String qq;

    public static class Phone {
        private String number;
        public static enum PhoneType {
            MP, TEL;
        }
        PhoneType type;

        public String getNumber() {
            return number;
        }

        public void setNumber(String number) {
            this.number = number;
        }

        public PhoneType getType() {
            return type;
        }

        public void setType(PhoneType type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return "Phone{" +
                    "number='" + number + '\'' +
                    ", type=" + type +
                    '}';
        }
    }
    private List<Phone> phones;

    public static class Address {
        private String home_address;
        private String unit_address;

        public String getHome_address() {
            return home_address;
        }

        public void setHome_address(String home_address) {
            this.home_address = home_address;
        }

        public String getUnit_address() {
            return unit_address;
        }

        public void setUnit_address(String unit_address) {
            this.unit_address = unit_address;
        }

        @Override
        public String toString() {
            return "Address{" +
                    "home_address='" + home_address + '\'' +
                    ", unit_address='" + unit_address + '\'' +
                    '}';
        }
    }
    private Address address;
    private HashMap<String, String> remark;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getQq() {
        return qq;
    }

    public void setQq(String qq) {
        this.qq = qq;
    }

    public List<Phone> getPhones() {
        return phones;
    }

    public void setPhones(List<Phone> phones) {
        this.phones = phones;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public HashMap<String, String> getRemark() {
        return remark;
    }

    public void setRemark(HashMap<String, String> remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "PeopleInfoEntity{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", qq='" + qq + '\'' +
                ", phones=" + phones +
                ", address=" + address +
                ", remark=" + remark +
                '}';
    }
}

效率对比测试代码

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import entity.PeopleInfoEntity;
import entityPB.Address;
import entityPB.PeopleInfoPB;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class TestJSON {
    private static PeopleInfoEntity createEntity() {
        PeopleInfoEntity peopleInfo = new PeopleInfoEntity();
        peopleInfo.setName("张三");
        peopleInfo.setAge(24);
        // 添加电话
        List<PeopleInfoEntity.Phone> phones = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            PeopleInfoEntity.Phone phone = new PeopleInfoEntity.Phone();
            phone.setNumber("193-0719-3096");
            phone.setType(PeopleInfoEntity.Phone.PhoneType.MP);
            phones.add(phone);
        }
        peopleInfo.setPhones(phones);
        peopleInfo.setQq("1969-612859");
        PeopleInfoEntity.Address address = new PeopleInfoEntity.Address();
        address.setHome_address("湖北省十堰市");
        address.setUnit_address("湖北省武汉市");
        peopleInfo.setAddress(address);
        HashMap<String, String> remark = new HashMap<String, String>() {{
            put("key1", "value1");
            put("key2", "value2");
            put("key3", "value3");
            put("key4", "value4");
            put("key5", "value5");
            put("key6", "value6");
        }};
        peopleInfo.setRemark(remark);
        return peopleInfo;
    }

    private static PeopleInfoPB createEntityPB() {
        PeopleInfoPB.Builder peopelInfoBuilder = PeopleInfoPB.newBuilder();
        peopelInfoBuilder.setName("张三");
        peopelInfoBuilder.setAge(24);
        // 设置 repeated+enum 数据类型
        for (int i = 1; i <= 5; i++) {
            PeopleInfoPB.Phone.Builder phoneBuilder = PeopleInfoPB.Phone.newBuilder();
            phoneBuilder.setNumber("193-0719-3096");
            phoneBuilder.setType(PeopleInfoPB.Phone.PhoneType.MP);
            peopelInfoBuilder.addPhone(phoneBuilder);
        }
        // 设置 oneof 数据类型
        peopelInfoBuilder.setQq("1969-612859");
        // 设置 Any 数据类型
        Address.Builder addressBuilder = Address.newBuilder();
        addressBuilder.setHomeAddress("湖北省十堰市");
        addressBuilder.setUnitAddress("湖北省武汉市");
        peopelInfoBuilder.setData(Any.pack(addressBuilder.build()));
        // 设置 map 备注
        peopelInfoBuilder.putRemark("key1", "value1");
        peopelInfoBuilder.putRemark("key2", "value2");
        peopelInfoBuilder.putRemark("key3", "value3");
        peopelInfoBuilder.putRemark("key4", "value4");
        peopelInfoBuilder.putRemark("key5", "value5");
        peopelInfoBuilder.putRemark("key6", "value6");
        return peopelInfoBuilder.build();
    }

    private static void testFastJSON2(PeopleInfoEntity peopleInfo, int count) {
        long beg = System.currentTimeMillis();
        String jsonStr = "";
        PeopleInfoEntity peopleInfo1 = null;
        for (int i = 0; i < count; i++) {
            jsonStr = JSON.toJSONString(peopleInfo);
        }
        long end = System.currentTimeMillis();
        System.out.printf("%d次 fastjson2 序列化耗时:%d ms;序列化后大小:%d\n", count, end - beg, jsonStr.length());
        beg = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            peopleInfo1 = JSON.parseObject(jsonStr, PeopleInfoEntity.class);
        }
        end = System.currentTimeMillis();
        System.out.printf("%d次 fastjson2 反序列化耗时:%d ms\n", count, end - beg);
    }

    private static void testJackson(PeopleInfoEntity peopleInfo, int count) throws JsonProcessingException {
        long beg = System.currentTimeMillis();
        String jsonStr = "";
        PeopleInfoEntity peopleInfo1 = null;
        ObjectMapper mapper = new ObjectMapper();
        for (int i = 0; i < count; i++) {
            jsonStr = mapper.writeValueAsString(peopleInfo);
        }
        long end = System.currentTimeMillis();
        System.out.printf("%d次 jackson   序列化耗时:%d ms;序列化后大小:%d\n", count, end - beg, jsonStr.length());
        beg = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            peopleInfo1 = mapper.readValue(jsonStr, PeopleInfoEntity.class);
        }
        end = System.currentTimeMillis();
        System.out.printf("%d次 jackson   反序列化耗时:%d ms\n", count, end - beg);
    }

    private static void testPB(PeopleInfoPB peopleInfo, int count) throws InvalidProtocolBufferException {
        long beg = System.currentTimeMillis();
        byte[] jsonBytes = null;
        PeopleInfoPB peopleInfo1 = null;
        for (int i = 0; i < count; i++) {
            jsonBytes = peopleInfo.toByteArray();
        }
        long end = System.currentTimeMillis();
        System.out.printf("%d次 PB        序列化耗时:%d ms;序列化后大小:%d\n", count, end - beg, jsonBytes.length);
        beg = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            peopleInfo1 = PeopleInfoPB.parseFrom(jsonBytes);
        }
        end = System.currentTimeMillis();
        System.out.printf("%d次 PB        反序列化耗时:%d ms\n", count, end - beg);
    }

    public static void main(String[] args) throws JsonProcessingException, InvalidProtocolBufferException {
        int count = 10000;
        // entity对象
        PeopleInfoEntity peopleInfoEntity = createEntity();
        testFastJSON2(peopleInfoEntity, count);
        testJackson(peopleInfoEntity, count);
        // pb对象
        PeopleInfoPB peopleInfoPB = createEntityPB();
        testPB(peopleInfoPB, count);
    }
}

在这里插入图片描述

发现PB的效率很快,而且序列化后文件大小也很小

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值