百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

深入浅出序列化(1)——JDK序列化和Hessian序列化

lipiwang 2025-05-26 17:13 10 浏览 0 评论

我之前在《聊一聊 RPC》中曾提过什么是序列化和反序列化,当时有说过之后要单独抽出一期来详细聊聊序列化,没想到这一拖竟然拖了一年多,现在来把这个坑补上。由于篇幅较长,本文先主要介绍两种常见的序列化方式——JDK 序列化和 Hessian 序列化。

序列化是什么(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 对象写入一个字节流:

public class Main {
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
            // 写入int:
            output.writeInt(12345);
            // 写入String:
            output.writeUTF("Hello");
            // 写入Object:
            output.writeObject(Double.valueOf(123.456));
        }
        System.out.println(Arrays.toString(buffer.toByteArray()));
    }
}

如果没有实现 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 来测试一下:

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

<!-- https://mvnrepository.com/artifact/com.caucho/hessian -->
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.65</version>
</dependency>

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

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

相关推荐

linux实例之设置时区的方式有哪些

linux系统下的时间管理是一个复杂但精细的功能,而时区又是时间管理非常重要的一个辅助功能。时区解决了本地时间和UTC时间的差异,从而确保了linux系统下时间戳和时间的准确性和一致性。比如文件的时间...

Linux set命令用法(linux cp命令的用法)

Linux中的set命令用于设置或显示系统环境变量。1.设置环境变量:-setVAR=value:设置环境变量VAR的值为value。-exportVAR:将已设置的环境变量VAR导出,使其...

python环境怎么搭建?小白看完就会!简简单单

很多小伙伴安装了python不会搭建环境,看完这个你就会了Python可应用于多平台包括Linux和MacOSX。你可以通过终端窗口输入"python"命令来查看本地是否...

Linux环境下如何设置多个交叉编译工具链?

常见的Linux操作系统都可以通过包管理器安装交叉编译工具链,比如Ubuntu环境下使用如下命令安装gcc交叉编译器:sudoapt-getinstallgcc-arm-linux-gnueab...

JMeter环境变量配置技巧与注意事项

通过给JMeter配置环境变量,可以快捷的打开JMeter:打开终端。执行jmeter。配置环境变量的方法如下。Mac和Linux系统在~/.bashrc中加如下内容:export...

C/C++|头文件、源文件分开写的源起及作用

1C/C++编译模式通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称...

linux中内部变量,环境变量,用户变量的区别

unixshell的变量分类在Shell中有三种变量:内部变量,环境变量,用户变量。内部变量:系统提供,不用定义,不能修改环境变量:系统提供,不用定义,可以修改,可以利用export将用户变量转为环...

在Linux中输入一行命令后究竟发生了什么?

Linux,这个开源的操作系统巨人,以其强大的命令行界面而闻名。无论你是初学者还是经验丰富的系统管理员,理解在Linux终端输入一条命令并按下回车后发生的事情,都是掌握Linux核心的关键。从表面上看...

Nodejs安装、配置与快速入门(node. js安装)

Nodejs是现代JavaScript语言产生革命性变化的一个主要框架,它使得JavaScript从一门浏览器语言成为可以在服务器端运行、开发各种各样应用的通用语言。在不同的平台下,Nodejs的安装...

Ollama使用指南【超全版】(olaplex使用方法图解)

一、Ollama快速入门Ollama是一个用于在本地运行大型语言模型的工具,下面将介绍如何在不同操作系统上安装和使用Ollama。官网:https://ollama.comGithub:http...

linux移植(linux移植lvgl)

1uboot移植l移植linux之前需要先移植一个bootlader代码,主要用于启动linux内核,lLinux系统包括u-boot、内核、根文件系统(rootfs)l引导程序的主要作用将...

Linux日常小技巧参数优化(linux参数调优)

Linux系统参数优化可以让系统更加稳定、高效、安全,提高系统的性能和使用体验。下面列出一些常见的Linux系统参数优化示例,包括修改默认配置、网络等多方面。1.修改默认配置1.1修改默认编辑器默...

Linux系统编程—条件变量(linux 条件变量开销)

条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等...

面试题-Linux系统优化进阶学习(linux系统的优化)

一.基础必备优化:1.关闭SElinux2.FirewalldCenetOS7Iptables(C6)安全组(阿里云)3.网络管理服务||NetworkManager|network...

嵌入式Linux开发教程:Linux Shell

本章重点介绍Linux的常用操作和命令。在介绍命令之前,先对Linux的Shell进行了简单介绍,然后按照大多数用户的使用习惯,对各种操作和相关命令进行了分类介绍。对相关命令的介绍都力求通俗易懂,都给...

取消回复欢迎 发表评论: