前言:本文将简单介绍一下protobuf的序列化,反序列化的基本概念,以及如何把java代码转化为二进制bit流。

一,序列化和反序列化

基本概念

序列化(Serialization)是将内存中复杂的对象结构转换为可以存储或传输的字节序列的过程。

序列化这个词初次听说可能是在计算机网络中的数据传输中听说到的,比如在TCP/IP
五层模型中,物理层的核心任务就是负责bit流的传输,bit流是数据包的本质

在网络通信中,对于发送方:应用层的代码会被序列化为字节数组,传输层对字节数组进行切片构造成数据包;而接收方接收到数据时,物理层设备(网卡或者光猫)也会按照一定的规则把bit流反序列化为数据包,向上传递给数据链路层。这就是序列化与反序列化在网络通信中的大致作用,可以看做是数据在字节序列和本体结构化的数据之间互相转化的一个过程
image.png

在数据存储中,序列化其实也有参与,但是和网络通信有所不同,不同在哪?下面是一个简单的区分:

  • 网络通信:网络通信是把传输的数据序列化工具在应用层把我们编写的代码转化为字节数组byte[ ].byte[ ]随后被写入Socket,之后会进入了内核态的 发送缓冲区(Send Buffer)。随后,操作系统的 TCP/IP 协议栈会接管这些数据。各层协议栈将其处理为一个个的数据包(package),用于网络中的数据传输

  • 数据存储:数据存储可以是把内存中的数据存储在硬盘中(文件)达到持久化存储的效果。毕竟内存中的数据可能会因为断电等情况导致丢失。写入磁盘之前,数据也需要进行序列化操作。
    经过protobuf序列化工具把数据转化为字节数组bytes[ ],这个字节数组可以通过java的OutputStream写入到本地文件中。

序列化发生在哪?、

这是一个层级关系与TCP/IP五层模型有关联,需要理清其作用位置

  • 序列化作用于: 代码 <-> 字节流 (bit流) 的转换。它完全属于应用层

  • Socket 作用于: 把应用层的 送给内核,让内核去拆分成

  • 物理层作用于: 里的 0101 变成 物理信号

总而言之,序列化发生在应用层,是数据经过处理变为字节数组的一个过程。

通过上面的简单介绍,对于序列化想必已经有了一个初步的认识,那个这个工具该如何使用呢?我们写的代码是怎么被编码为字节数组中?下面来简单介绍。

如何安装这个工具,这里并不涉及,过程很简单,相信阅读到此处的你不会被其难倒。(此处省略1000字…)

二,protobuf基本使用

定义消息(message)

message类似于C语言中的结构体(struct)或者java中的类,是定位一个结构化的数据的基本单位

message 用于声明一个逻辑对象。在 .proto 文件中定义一个 message 后,编译器会根据目标语言(如 Java)生成对应的实体类和构建者(Builder)。

下面来创建一个message消息

//语法指定行  
syntax = "proto3";  
package start;  
  
option java_multiple_files = true;//为每个 message 生成独立的 .java 文件  
option java_package = "com.example.start";//指定生成的 Java 类所属的包路径  
option java_outer_classname = "ContactsProtos";//指定外部主类的名称  
  
//定义联系人message  
message  PeopleInfo{  
  //字段类型 字段名 = 字段唯一编号  
  string name = 1;  
  int32 age = 2;  
}
  • 字段类型 (Field Type):如 int32int64stringbool 等。

  • 字段名称 (Field Name):建议使用小写加下划线(如 user_name),编译器生成 Java 代码时会自动转为驼峰命名。

  • 字段编号 (Field Tag):这是 Protobuf 极其重要的特性。

    • 唯一性:同一个 message 内的每个字段必须有一个唯一的编号。

    • 二进制标识:在序列化后的二进制流中,Protobuf 不存储字段名,而是存储编号。

    • 性能优化:编号 115 在编码时只占 1 个字节,因此应将最频繁出现的字段分配给这些小编号。这在离散数学中的章节中也有涉及到。其实就是把频繁出现的热数据用占用字节更少的编号,这样可以一定程度上提高传输效率

通过上面的方法我们通过java代码定义了一个PeopleInfo,下面是把他编译为字节数组的教程,我们通过使用插件来完成

使用插件序列化代码

注意不要全部复制粘贴,根据个人电脑的情况修改目录路径

<plugin>  
    <groupId>org.xolstice.maven.plugins</groupId>  
    <artifactId>protobuf-maven-plugin</artifactId>  
    <version>0.6.1</version>  
    <configuration>        <!-- 本地安装的protoc.exe的⽬录 -->  
        <protocExecutable>D:\protoc-34.0-win64\bin\protoc.exe</protocExecutable>  
        <!-- proto⽂件放置的⽬录,默认为/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>

配置完成后注意刷新maven,按照下面图片中步骤使用插件编译,看到三个编译成功的文件

image.png

生成的关键类分析

下面是一个简单的对于这些类的解读

  • ContactsProtos:这是在 java_outer_classname 中定义的外部主类,作为整个文件的容器。都为静态方法

  • PeopleInfo:这是核心类,包含了 nameage 字段(可以通过get方法获取或者set方法进行设置message的属性)。它提供了 Builder 内部类用于构建对象,以及toByteArray() 方法用于序列化。反序列化方法在

  • PeopleInfoOrBuilder:这是一个接口,定义了 getName()getAge() 等只读方法。生成的类和 Builder 都会实现它,确保了访问数据的一致性.

image.png
找不到的话在对应文件中按ctrl + F12 ,ctrl + F搜索即可

下面来简单创建一个类使用序列化和反序列化方法,来观察protobuf序列化和反序列化的效果

为了一致性,我在com.example.start对应的包中新建一个测试类

代码如下:

package com.example.start;  
  
import com.google.protobuf.InvalidProtocolBufferException;  
  
import java.lang.reflect.Array;  
import java.util.Arrays;  
  
public class FastStart {  
    public static void main(String[] args) throws InvalidProtocolBufferException {  
        PeopleInfo p1 = PeopleInfo.newBuilder()  
                .setName("张三")  
                .setAge(16)  
                .build();  
        //序列化  
        byte[] bytes =  p1.toByteArray();//使用字节数组存储序列化内容  
        System.out.println("序列化内容为:"+ Arrays.toString(bytes));//byte数组无法直接打印,转为字符串打印
  
        //反序列化  
        PeopleInfo p2 = PeopleInfo.parseFrom(bytes);  
        System.out.println("反序列化内容为:");  
        System.out.println("姓名:" + p2.getName());  
        System.out.println("年龄:" + p2.getAge());  
    }  
}

上面的大致流程可以分为以下:

  • 1.创建对象newBuilder() -> build()
  • 2,序列化toByteArray()
  • 3.反序列化parseFrom()
  • 4.打印观察效果:通过getAge等方法获取字段值
    运行结果如下显示:
    image.png

注意这里byte数组中的二进制已经被转化为方便阅读的十进制数了,所以不要奇怪为什么不是显示二进制序列

本文就简单讲到这里,如有纰漏还请指出~~

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐