序列化系列(1)——JDK序列化和Hessian序列化

2021-08-06 11:10:22 浏览数 (1)

我之前在《聊一聊

序列化是什么(What)

百度百科对于 「序列化」 的解释是:

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。

这么说太抽象了,举一个例子:你如果想让一个女孩子知道你喜欢她,你可以给她写情书,这样 「喜欢」 这种状态信息就变成了 「文字」 这种可以存储或传输的信息。

至于怎么把“情书”送给女生就有很多种方式了,我在《聊一聊RPC》中已经有写过了,感兴趣的读者们可以点击阅读。

所以,简单理解序列化就是将“对象”存储的信息保存到某个“文件”中,之后再通过某种方式读取“文件”转换成对象。在Java中,序列化其实就是把一个Java对象变成二进制内容,本质上就是一个byte[]数组。

既然有序列化,那么就会有反序列化,在上文的例子中,如果女孩通过情书中的文字明白了男孩的喜欢,这就是一种反序列化。在Java中,将一个byte[]数组重新变成Java对象就是一种反序列化。

为什么要序列化(Why)

这个时候肯定就有人会问了,直接把对象作为参数传递不就可以了吗?为什么还要多此一举把对象变成“文本”,然后再将“文本”变成对象?

我们知道,Java创建的对象都是存在于Java虚拟机中,也即JVM中,那么JVM又在哪里呢?

JVM是Java程序运行的环境,但是他同时是一个操作系统的一个应用程序,即一个进程。他的运行依赖于内存,因此Java中对象都是存储在内存中,准确地说是JVM的堆或栈内存中,可以各个线程之间进行对象传输,但是无法在进程之间进行传输。如果涉及到跨内存的数据传输(比如两台机器的传输),直接把对象作为参数传递就不可取了,这时就需要通过“网络”将数据传输。

举个例子,如果没办法自己亲自把情书送到对方手上,是不是得找一个人送过去?这就是RPC相关的知识了。

怎么序列化(How)

上述内容在之前那篇文章里都有涉及,接下来才是本文的重点,在实际使用时我们究竟该怎么序列化,有哪些方式可以序列化?为什么我们在代码中很少遇到手写序列化的情况。这些都是本文要解答的内容。

本文我们以Java为例。

JDK 序列化

作为一个成熟的编程语言,Java本身就已经提供了序列化的方法了,因此我们也选择把他作为第一个介绍的序列化方式。

JDK自带的序列化方式,使用起来非常方便,只需要序列化的类实现了Serializable接口即可,Serializable接口没有定义任何方法和属性,所以只是起到了标识的作用,表示这个类是可以被序列化的。如果想把一个Java对象变为byte[]数组,需要使用ObjectOutputStream。它负责把一个Java对象写入一个字节流:

代码语言:javascript复制

如果没有实现Serializable接口而进行序列化操作就会抛出NotSerializableException异常

上面代码输出的是如下的Byte数组:

serialVersionUID

在反序列化时,JVM需要知道所属的class文件,在序列化的时候JVM会记录class文件的版本号,也即serialVersionUID这一变量。该变量默认是由JVM自动生成,也可以手动定义。反序列化时JVM会按版本号找指定版本的class文件进行反序列化,如果class文件有版本号在序列化和反序列化时不一致就会导致反序列化失败,会抛异常提示版本号不一致,

特点

JDK序列化会把对象类的描述和所有属性的元数据都序列化为字节流,另外继承的元数据也会序列化,所以导致序列化的元素较多且字节流很大,但是由于序列化了所有信息所以相对而言更可靠。但是如果只需要序列化属性的值时就比较浪费。

而且因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。

其次,由于这种方式是JDK自带,无法被多个语言通用,因此通常情况下不会使用该种方式进行序列化。

Hessian

Hessian 是一种动态类型、二进制序列化和 Web 服务协议,专为面向对象的传输而设计。

官方介绍?Hessian 2.0 Serialization Protocol[1]

和JDK自带的序列化方式类似,Hessian采用的也是二进制协议,只不过Hessian序列化之后,字节数更小,性能更优。目前Hessian已经出到2.0版本,相较于1.0的Hessian性能更优。相较于JDK自带的序列化,Hessian的设计目标更明确?

序列化实现方式

之所以说Hessian序列化之后的数据字节数更小,和他的实现方式密不可分,以int 存储整数为例,我们通过官网的说明可以发现存储逻辑如下?

翻译一下就是:

一个32位有符号的整数。一个整数由八位数x49('I')表示,后面是整数的4个八位数,以高位优先(big-endian)顺序排列。

简单来说就是,如果要存储一个数据为1的整数,Hessian会存储成I 1这样的形式。

存对象也很简单,如下:

对于Hessian支持的数据结构,官网均有序列化的语法,详情可参考?Serialization[2]

而且,和JDK自带序列化不同的是,如果一个对象之前出现过,hessian会直接插入一个R index这样的块来表示一个引用位置,从而省去再次序列化和反序列化的时间。

也正是因为如此,Hessian的序列化速度相较于JDK序列化才更快。只不过Java序列化会把要序列化的对象类的元数据和业务数据全部序列化从字节流,并且会保留完整的继承关系,因此相较于Hessian序列化更加可靠。

不过相较于JDK的序列化,Hessian另一个优势在于,这是一个跨语言的序列化方式,这意味着序列化后的数据可以被其他语言使用,兼容性更好。

不服跑个分?

空口无凭,我们以一个小demo来测试一下:

使用之前记得先引入依赖哦

代码语言:javascript复制
<!-- https://mvnrepository.com/artifact/com.caucho/hessian -->
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.65</version>
</dependency>

运行的结果也和我们预想的一样:

代码语言:javascript复制
hessian序列化长度:47
jdk序列化长度:99

总结

通过上文的介绍,想必你也了解了JDK自带序列化方式和Hessian序列化之间的一些区别了,JDK序列化选择将所有的信息保存下来,因此可靠性更好。与此同时,由于采取了不同的序列化方案,Hessian在体积和速度上相较于JDK序列化更优秀,且由于Hessian设计之初就考虑到跨语言的需求因此在兼容性方面也更胜一筹。

之后我们将会介绍其他的一些序列化协议,如果你觉得本文对你有所帮助,不妨点赞关注支持一下~

References

[1] Hessian 2.0 Serialization Protocol: http://hessian.caucho.com/doc/hessian-serialization.html [2] Serialization: http://hessian.caucho.com/doc/hessian-serialization.html#anchor4

0 人点赞