序列化和反序列化

我们在开发一些远程过程调用(RPC)的程序时,通常会涉及对象的序列化/反序列化

的问题,例如一个“Person”对象从客户端通过TCP方式发送到服务器端。由于TCP协议

(或者UDP等类似低层协议)只能发送字节流,所以需要应用层将Java POJO对象“序列

化”成字节流,发送过去之后,数据接收端再将字节流“反序列化”化成Java POJO对象即

可。

一、序列化和反序列化

“序列化”和“反序列化”一定会涉及POJO的编码和格式化(Encoding & Format),

目前我们可选择的编码方式有:

⚫ 使用JSON。将Java POJO对象转换成JSON结构化字符串。基于HTTP协议,在Web

应用、移动开发方面等,这是常用的编码方式,因为JSON的可读性较强。但是它

的性能稍差。

⚫ 基于XML。和JSON一样,数据在序列化成字节流之前都转换成字符串。可读性

强,性能差,异构系统、Open API类型的应用中常用。

⚫ 使用Java内置的编码和序列化机制,可移植性强,性能稍差,无法跨平台(语

言)。

⚫ 开源的二进制的序列化/反序列化框架,例如Apache Avro,Apache Thrift、

Protobuf等。前面的两个框架和Protobuf相比,性能非常接近,而且设计原理如出

一辙;其中Avro在大数据存储(RPC数据交换、本地存储)时比较常用;Thrift的

亮点在于内置了RPC机制,所以在开发一些RPC交互式应用时,客户端和服务器

端的开发与部署都非常简单。

如何选择序列化/反序列化框架呢?

评价一个序列化框架的优缺点,大概从两个方面着手:

(1)结果数据大小,原则上说,序列化后的数据尺寸越小,传输效率越高。

(2)结构复杂度,这会影响序列化/反序列化的效率,结构越复杂,越耗时。

理论上来说,对于对性能要求不是太高的服务器程序,可以选择JSON文本格式的序列

化框架;对于性能要求比较高的服务器程序,则应该选择传输效率更高的二进制序列化框

架,建议是Protobuf。

Protobuf是一个高性能、易扩展的序列化框架,性能比较高,其性能的有关的数据可以

参看官方文档。Protobuf本身非常简单,易于开发,而且结合Netty框架,可以非常便捷地

实现一个通信应用程序。反过来,Netty也提供了相应的编解码器,为Protobuf解决了有关

Socket通信中“半包、粘包”等问题。

当然,无论是使用JSON、Protobuf还是其他的传输协议,我们必须保证在数据包的反序列化之前,接收端的ByteBuf二进制数据包一定是一个完整的应用层二进制包,不能是一

个半包或者粘包,这就涉及到通信过程中的拆包技术。

二、几种序列化工具箱

使用JSON协议通信

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于

ECMAScript (欧洲计算机协会制定的JS规范)的一个子集,采用完全独立于编程语言的文本

格式来存储和表示数据。简洁和清晰的层次结构使得JSON成为理想的数据交换语言。

JSON协议是一种文本协议,易于人阅读和编写,同时也易于机器解析和生成,并能有

效地提升网络传输效率。

JSON的核心优势

XML也是一种常用的文本协议,XML和JSON都使用结构化方法来标记数据。和XML

相比,JSON作为数据包格式传输的时候具有更高的效率,这是因为JSON不像XML那样需

要有严格的闭合标签,这就让有效数据量与总数据包比大大提升,从而同等数据流量的情

况下,JSON减少网络的传输压力。

下面来做一个简单的比较。用XML表示中国部分省市数据如下:

JSON的核心优势
XML也是一种常用的文本协议,XML和JSON都使用结构化方法来标记数据。和XML
相比,JSON作为数据包格式传输的时候具有更高的效率,这是因为JSON不像XML那样需
要有严格的闭合标签,这就让有效数据量与总数据包比大大提升,从而同等数据流量的情
况下,JSON减少网络的传输压力。
下面来做一个简单的比较。用XML表示中国部分省市数据如下:


中国

广东

广州
深圳



新疆

乌鲁木齐



以上中国部分省市数据用JSON表示如下:
{
"name": "中国",
"province": [
{
"name": "广东",
"cities": {
"city": ["广州", "深圳"]
}
}, {
"name": "新疆",
"cities": {
"city": ["乌鲁木齐"]
}

可以看到,JSON的语法格式和清晰的层次结构非常简单,明显要比 XML 容易阅

读,并且在数据交换方面,由于JSON所使用的字符要比XML少得多,可以大大得节约传

输数据所占用的带宽。

Java处理JSON数据有三个比较流行的开源类库有:阿里的FastJson、谷歌的Gson和开

源社区的Jackson。

Jackson是一个简单的、基于Java的JSON开源库。使用Jackson开源库,可以轻松地将

Java POJO对象转换成JSON、XML格式字符串;同样也可以方便地将JSON、XML字符串

转换成Java POJO对象。Jackson开源库的优点是:所依赖的Jar包较少、简单易用、性能也

还不错,另外Jackson社区相对比较活跃。Jackson开源库的缺点是:对于复杂POJO类型、

复杂的集合Map、List的转换结果,不是标准的JSON格式,或者会出现一些问题。

Google的Gson开源库是一个功能齐全的JSON解析库,起源于Google公司内部需求而由

Google自行研发而来,在2008年5月公开发布第一版之后已被许多公司或用户应用。Gson

可以完成复杂类型的POJO和JSON字符串的相互转换,转换的能力非常强。

阿里巴巴的FastJson是一个高性能的JSON库。顾名思义,FastJson库采用独创的快速算

法,将JSON转成POJO的速度提升到极致,从性能上说,其反序列化速度超过其他JSON开

源库。传闻说FastJson在复杂类型的POJO转换JSON(序列化)时,可能会出现一些引用类型问题而导致JSON转换出错,需要进行引用的定制(亲自实验过,对于嵌套的java的对象是无法直接反序列化的)。

在实际开发中,目前主流的策略是:Google的Gson库和阿里的FastJson库两者结合使

用。在POJO序列化成JSON字符串的应用场景(序列化场景),使用Google的Gson库;在

JSON字符串反序列化成POJO的应用场景(反序列化场景),使用阿里的FastJson库。

使用Protobuf协议通信

Protobuf全称是Google Protocol Buffer,Google提出的一种数据交换的格式,是一套类

似JSON或者XML的数据传输格式和规范,用于不同应用或进程之间进行通信。Protobuf具

有以下特点:

(1)语言无关,平台无关

Protobuf支持Java、 C++,、Python、JavaScript等多种语言,支持跨多个平台。

(2)高效

比XML更小(3~10倍),更快(20 ~ 100倍),更为简单。

(3)扩展性,兼容性好

可以更新数据结构,而不影响和破坏原有的旧程序。

Protobuf既独立于语言,又独立于平台。Google官方提供了多种语言的实现:Java、

C#、C++、GO、JavaScript和Python。Protobuf的编码过程为:使用预先定义的Message数据

结构将实际的传输数据进行打包,然后编码成二进制的码流进行传输或者存储。Protobuf的

解码过程则刚好与编码过程相反:将二进制码流解码成Protobuf自己定义的Message结构的

POJO实例。

与JSON、XML相比,Protobuf算是后起之秀,只是Protobuf更加适合于高性能、快速

响应的数据传输应用场景。Protobuf数据包是一种二进制的格式,相对于文本格式的数据交

换(JSON、XML)来说,速度要快很多。由于Protobuf优异的性能,使得它更加适用于分

布式应用场景下的数据通信或者异构环境下的数据交换。

另外,JSON、XML是文本格式,数据具有可读性;而Protobuf是二进制数据格式,数

据本身不具有可读性,只有反序列化之后才能得到真正可读的数据。正因为Protobuf是二进

制数据格式,数据序列化之后,体积相比JSON和XML要小,更加适合网络传输。

总体来说,在一个需要大量数据传输的应用场景中,因为数据量很大,那么选择

Protobuf可以明显地减少传输的数据量和提升网络IO的速度。对于打造一款高性能的通信

服务器来说,Protobuf传输协议是最高性能的传输协议之一。微信的消息传输就采用了

Protobuf协议。

详解Protobuf协议语法

在Protobuf中,通信协议的格式是通过“.proto”文件定义的。一个“.proto”文件有两

大组成部分:头部声明、消息结构体的定义。头部声明的部分,主要包含了协议的版本、

包名、特定语言的选项设置等;消息结构体部分,可以定义一个或者多个消息结构体。

在Java中,当用Protobuf编译器(如“protoc3.6.1.exe”)来编译“.proto”文件时,编

译器将生成Java语言的POJO消息类和Builder构造者类。通过POJO消息类和Builder构造

者,Java程序可以很容易地操作在.proto文件中定义的消息和字段:包括获取、设置字段

值,将消息序列化到一个输出流中(序列化),以及从一个输入流中解析消息(反序列

化)。

官方文档:

https://developers.google.com/protocol-buffers/docs/proto3

proto描述文件的编写

syntax = "proto3";

message gps_data {
    int64 id = 1;
	string terminalId = 2;
	string dataTime = 3;
	double lon = 4;
	double lat = 5;
	float speed = 6;
	int32 altitude = 7;
	int32 locType = 8;
	int32 gpsStatus = 9;
	float direction = 10;
	int32 satellite = 11;
}

注意:
* 顶部必须申明句法的版本号,如果不申明,则默认是2.0的语法。
* 下面Message节里面的字段就是业务需要的各个字段了,等号后面的数字是序号,必须指定。在3.5版本中不用指定required、等关键字了。
* gps_data 的名称格式与为生成的java文件的名称是有关系的,如果加入了下划线,则默认生成的是GpsData 这个驼峰格式的名称。当然你也可以在文件里自定义java文件的名称

如下:

option java_outer_classname = "BatteryData";
但是,生成的java代码中的的builder还是按照默认格式来生成的,所以,建议大家就直接按照默认规则来设计即可。
* 此外,还可以指定改java代码的包路径,命令如下:

option java_package = "com.yjgis.test"; 
但是这里不建议使用这个功能,因为包路径会写入到生成的代码中,一旦,代码进行重构的时候,修改起来会很麻烦,还不如直接把包路径这种功能交给ide来完成

JAVA代码的生成
生成JAVA模型的命令
在命令行工具中敲入下面的命令
protoc -I=src/main/resource/proto 
--java_out=src/main/java src/main/resource/proto/protobuf.proto
说明:
-I 后面是proto文件所在的目录,
–java_out 后面是生成java文件存放地址
最后一行是proto文件的名称,可以写绝对地址,也可以直接写proto文件名称

生成的代码如下所示

序列化和反序列化_第1张图片

绿色即是上面文件生成的代码。

在JAVA中如何使用生成的数据模型

把生成的这个java类拷贝到工程,如下图:

序列化和反序列化_第2张图片

主要需要使用的对象就是绿色方框内的,下面,我们来写一些代码

  • 测试代码
    有如下三个功能即可满足在业务中的需要了,如下:
    • 使用java代码生成一个数据模型
    • 把模型转换为二进制数据流
    • 把二进制数据流转成java对象

首先在测试工程pom里增加依赖

pom:


    com.google.protobuf
    protobuf-java
    3.5.0


JAVA代码:

import com.google.protobuf.InvalidProtocolBufferException;

public class TestGpsProtobuf {
    public static void main(String[] args) {
        System.out.println("===== 构建一个GPS模型开始 =====");
        GpsData.gps_data.Builder gps_builder = GpsData.gps_data.newBuilder();
        gps_builder.setAltitude(1);
        gps_builder.setDataTime("2017-12-17 16:21:44");
        gps_builder.setGpsStatus(1);
        gps_builder.setLat(39.123);
        gps_builder.setLon(120.112);
        gps_builder.setDirection(30.2F);
        gps_builder.setId(100L);

        GpsData.gps_data gps_data = gps_builder.build();
        System.out.println(gps_data.toString());
        System.out.println("===== 构建GPS模型结束 =====");

        System.out.println("===== gps Byte 开始=====");
        for(byte b : gps_data.toByteArray()){
            System.out.print(b);
        }
        System.out.println("\n" + "bytes长度" + gps_data.toByteString().size());
        System.out.println("===== gps Byte 结束 =====");

        System.out.println("===== 使用gps 反序列化生成对象开始 =====");
        GpsData.gps_data gd = null;
        try {
            gd = GpsData.gps_data.parseFrom(gps_data.toByteArray());
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        System.out.print(gd.toString());
        System.out.println("===== 使用gps 反序列化生成对象结束 =====");

    }
}

运行一下:

Connected to the target VM, address: '127.0.0.1:59012', transport: 'socket'
===== 构建一个GPS模型开始 =====
id: 100
dataTime: "2017-12-17 16:21:44"
lon: 120.112
lat: 39.123
altitude: 1
gpsStatus: 1
direction: 30.2

===== 构建GPS模型结束 =====
===== gps Byte 开始=====
810026195048495545495045495532495458504958525233-707312243794644157-76-56118-66-113676456172185-102-103-1565
bytes长度50
===== gps Byte 结束 =====
===== 使用gps 反序列化生成对象开始 =====
id: 100
dataTime: "2017-12-17 16:21:44"
lon: 120.112
lat: 39.123
altitude: 1
gpsStatus: 1
direction: 30.2
===== 使用gps 反序列化生成对象结束 =====

如何快速的进行json格式化

我们在使用protobuf的时候,有些场景是需要快速把protobuf生成的对象转成json的,protobuf提供了很方便的方法来实现这个功能。但是这个功能并不包含在核心包里,需要依赖protobuf的工具包。
代码如下:

pom:

    com.google.protobuf
    protobuf-java-util
    3.5.0


 System.out.println("===== 使用gps 转成json对象开始 =====");

 String jsonFormatM = "";
 try {
     jsonFormatM = JsonFormat.printer().print(gd);
 } catch (Exception e) {
     e.printStackTrace();
 }

 System.out.println(jsonFormatM.toString());
 System.out.println("json数据大小:" + jsonFormatM.getBytes().length);
 System.out.println("===== 使用gps 转成json对象结束 =====");

执行结果:

===== 使用gps 转成json对象开始 =====
{
  "id": "100",
  "dataTime": "2017-12-17 16:21:44",
  "lon": 120.112,
  "lat": 39.123,
  "altitude": 1,
  "gpsStatus": 1,
  "direction": 30.2
}
json数据大小:145
===== 使用gps 转成json对象结束 =====

可以看到protobuf数据大小是json的1/3,如果数据模型更复杂或者数据量更大,优势还会更加明显。

这里只是为了展示基础用法,实际上通过Maven插件生成POJO和Builder是更好的选择

具体使用参考下文:

几种序列化方式的总结(Serializable,json,Fastjson,ProtoBuff,Hessian和Kyro)_HX_2022的博客-CSDN博客_serializable与jackson

三、Spring整合redis序列化问题处理 

https://www.csdn.net/tags/Mtzakg5sNTAwMTgtYmxvZwO0O0OO0O0O.html

​​​​​​Spring Session基于Redis存储的序列化问题 - 溪水静幽 - 博客园

 spring-session 面对redis配置序列化无效后的解决方式_迷茫路人的博客-CSDN博客

Spring Boot2.X 自定义Redis的cacheManager,保存Json格式到Redis_caojidasabi的博客-CSDN博客_自定义rediscachemanager

Redis读取数据将复杂对象的List读取为Map_Y.R.J的博客-CSDN博客

你可能感兴趣的:(日常编程,记录,java,redis,spring)