1、前言
我们应该思考下面几个问题:
- 序列化和反序列化到底是什么?
- 它的主要使用场景有哪些?
- Java 序列化常见的方案有哪些?
- 各种常见序列化方案的区别有哪些?
- 实际的业务开发中有哪些坑点?
接下来将从这几个角度去研究这个问题。
2. 序列化和反序列化是什么?为什么需要它?
序列化是将内存中的对象信息转化成可以存储或者传输的数据到临时或永久存储的过程。而反序列化正好相反,是从临时或永久存储中读取序列化的数据并转化成内存对象的过程。
那么为什么需要序列化和反序列化呢?
希望大家能够养成从本源上思考这个问题的思维方式,即思考它为什么会出现,而不是单纯记忆。
大家可以回忆一下,平时都是如果将文字文件、图片文件、视频文件、软件安装包等传给小伙伴时,这些资源在计算机中存储的方式是怎样的。
进而再思考,Java 中的对象如果需要存储或者传输应该通过什么形式呢?
我们都知道,一个文件通常是一个 m 个字节的序列:B0, B1, …, Bk, …, Bm-1。所有的 I/O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对应文件的读和写来执行。
因此本质上讲,文本文件,图片、视频和安装包等文件底层都被转化为二进制字节流来传输的,对方得文件就需要对文件进行解析,因此就需要有能够根据不同的文件类型来解码出文件的内容的程序。
大家试想一个典型的场景:如果要实现 Java 远程方法调用,就需要将调用结果通过网路传输给调用方,如果调用方和服务提供方不在一台机器上就很难共享内存,就需要将 Java 对象进行传输。而想要将 Java 中的对象进行网络传输或存储到文件中,就需要将对象转化为二进制字节流,这就是所谓的序列化。存储或传输之后必然就需要将二进制流读取并解析成 Java 对象,这就是所谓的反序列化。
序列化的主要目的是:方便存储到文件系统、数据库系统或网络传输等。
实际开发中常用到序列化和反序列化的场景有:
- 远程方法调用(RPC)的框架里会用到序列化。
- 将对象存储到文件中时,需要用到序列化。
- 将对象存储到缓存数据库(如 Redis)时需要用到序列化。
- 通过序列化和反序列化的方式实现对象的深拷贝。
3. 常见的序列化方式
常见的序列化方式包括 Java 原生序列化、Hessian 序列化、Kryo 序列化、JSON 序列化等。
3.1 Java 原生序列化
正如前面章节讲到的,对于 JDK 中有的类,最好的学习方式之一就是直接看其源码。
Serializable
的源码非常简单,只有声明,没有属性和方法:
// 注释太长,省略
public interface Serializable {
}
在学习源码注释之前,希望大家可以站在设计者的角度,先思考一个问题:如果一个类序列化到文件之后,类的结构发生变化还能否保证正确地反序列化呢?
答案显然是不确定的。
那么如何判断文件被修改过了呢? 通常可以通过加密算法对其进行签名,文件作出任何修改签名就会不一致。但是 Java 序列化的场景并不适合使用上述的方案,因为类文件的某些位置加个空格,换行等符号类的结构没有发生变化,这个签名就不应该发生变化。还有一