序列化
865字约3分钟
2024-08-08
序列化是指把一个 Java
对象变成二进制内容,本质上就是一个 byte[]
数组
为什么要把Java对象序列化呢?
因为序列化后可以把 byte[]
保存到文件中,或者把 byte[]
通过网络传输到远程,这样,就相当于把 Java
对象存储到文件或者通过网络传输出去了
有序列化,就有反序列化,即把一个二进制内容(也就是 byte[]
数组)变回Java对象。有了反序列化,保存到文件中的 byte[]
数组又可以"变回" Java
对象,或者从网络上读取 byte[]
并把它"变回" Java
对象
一个 Java
对象要能序列化,必须实现一个特殊的 java.io.Serializable
接口,它的定义如下
public interface Serializable {
}
Serializable
接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为"标记接口"(Marker Interface
),实现了标记接口的类仅仅是给自身贴了个"标记",并没有增加任何方法
序列化
把一个 Java
对象变为 byte[]
数组,需要使用 ObjectOutputStream
。它负责把一个 Java
对象写入一个字节流
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()));
}
ObjectOutputStream
既可以写入基本类型,如 int
,boolean
,也可以写入 String
(以 UTF-8
编码),还可以写入实现了 Serializable
接口的 Object
因为写入 Object
时需要大量的类型信息,所以写入的内容很大
反序列化
和 ObjectOutputStream
相反,ObjectInputStream
负责从一个字节流读取 Java
对象
try (ObjectInputStream input = new ObjectInputStream(...)) {
int n = input.readInt();
String s = input.readUTF();
Double d = (Double) input.readObject();
}
readObject()
可能抛出的异常有
ClassNotFoundException
:没有找到对应的Class
InvalidClassException
:Class
不匹配
InvalidClassException
,这种情况常见于序列化的 Person
对象定义了一个 int
类型的 age
字段,但是反序列化时,Person
类定义的 age
字段被改成了 long
类型,所以导致 class
不兼容
为了避免这种 class
定义变动导致的不兼容,Java
的序列化允许 class
定义一个特殊的 serialVersionUID
静态变量,用于标识 Java
类的序列化"版本",通常可以由 IDE
自动生成。如果增加或修改了字段,可以改变 serialVersionUID
的值,这样就能自动阻止不匹配的class
版本
public class Person implements Serializable {
private static final long serialVersionUID = 2709425275741743919L;
}
特别注意反序列化特点
反序列化时,由 JVM
直接构造出 Java
对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行
安全性
因为 Java
的序列化机制可以导致一个实例能直接从 byte[]
数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的 byte[]
数组被反序列化后可以执行特定的 Java
代码,从而导致严重的安全漏洞
实际上,Java
本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过 JSON
这样的通用数据结构来实现,只输出基本类型(包括 String
)的内容,而不存储任何与代码相关的信息