序列化与反序列化的基本原理
序列化 是将对象的状态转换为字节流的过程,以便保存到文件、数据库或通过网络传输。Java 提供了 java.io.Serializable
接口来实现对象的序列化。任何需要序列化的类都必须实现这个接口。
反序列化 是将字节流转换回对象的过程,恢复对象的状态。Java 提供了 ObjectInputStream
类来读取序列化的对象,并将其转换回原始对象。
序列化的基本步骤
- 实现
Serializable
接口 :需要序列化的类必须实现Serializable
接口。 - 使用
ObjectOutputStream
:将对象写入输出流。 - 使用
ObjectInputStream
:从输入流中读取对象。
代码示例
以下代码展示了如何将一个 User
对象序列化到文件中,并从文件中反序列化回来。
package chapter08;
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
File dataFile = new File("user.dat");
// 序列化
try (ObjectOutputStream objectOut = new ObjectOutputStream(new FileOutputStream(dataFile))) {
User user = new User("Alice", 25);
objectOut.writeObject(user);
System.out.println("序列化后的对象:" + user);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream objectIn = new ObjectInputStream(new FileInputStream(dataFile))) {
User user = (User) objectIn.readObject();
System.out.println("反序列化后的对象:" + user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + '}';
}
}
运行结果
序列化后的对象:User{name='Alice', age=25}
反序列化后的对象:User{name='Alice', age=25}
异常处理
在进行文件操作和对象序列化时,可能会遇到以下几种异常:
- FileNotFoundException:文件未找到异常,通常在尝试打开一个不存在的文件时发生。
- IOException:输入输出异常,通常在读写过程中发生。
- ClassNotFoundException:类未找到异常,通常在反序列化时发生,表明序列化流中的类在当前环境中不可用。
- NotSerializableException :对象未实现
Serializable
接口,无法进行序列化。
异常处理示例
package chapter08;
import java.io.*;
public class ExceptionHandlingExample {
public static void main(String[] args) {
FileInputStream in = null;
ObjectInputStream objIn = null;
ObjectOutputStream objOut = null;
try {
in = new FileInputStream("nonexistentfile.txt");
objOut = new ObjectOutputStream(new FileOutputStream("user.dat"));
objOut.writeObject(new User("Bob", 30));
objIn = new ObjectInputStream(new FileInputStream("user.dat"));
User user = (User) objIn.readObject();
System.out.println("读取的对象:" + user);
} catch (FileNotFoundException e) {
System.err.println("文件未找到:" + e.getMessage());
} catch (IOException e) {
System.err.println("输入输出异常:" + e.getMessage());
} catch (ClassNotFoundException e) {
System.err.println("类未找到:" + e.getMessage());
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
System.err.println("关闭输入流异常:" + e.getMessage());
}
}
if (objIn != null) {
try {
objIn.close();
} catch (IOException e) {
System.err.println("关闭对象输入流异常:" + e.getMessage());
}
}
if (objOut != null) {
try {
objOut.close();
} catch (IOException e) {
System.err.println("关闭对象输出流异常:" + e.getMessage());
}
}
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + '}';
}
}
运行结果
文件未找到:nonexistentfile.txt (系统找不到指定的文件。)
读取的对象:User{name='Bob', age=30}
自定义序列化
有时我们需要自定义序列化过程,Java 提供了 writeObject
和 readObject
方法来实现这一点。
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient int age; // 不希望序列化的字段使用 transient 关键字
public User(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 默认序列化
out.writeInt(age); // 自定义序列化
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 默认反序列化
age = in.readInt(); // 自定义反序列化
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + '}';
}
}
版本控制
序列化的类应定义一个唯一的序列版本号 serialVersionUID
,以确保在反序列化时版本的兼容性。
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 构造函数、getter、setter 和其他方法
}
使用场景
- 持久化存储:将对象保存到文件或数据库中。
- 网络传输:在分布式系统中传输对象。
- 深拷贝:通过序列化和反序列化实现对象的深拷贝。
实际应用中的注意事项
- 安全性:序列化和反序列化可能会带来安全风险,特别是在反序列化不受信任的数据时,可能会引发反序列化漏洞。
- 性能:序列化和反序列化的性能可能成为瓶颈,特别是在处理大量数据时。
- 兼容性:确保不同版本的类在序列化和反序列化时的兼容性。
总结
- 序列化和反序列化:用于将对象转换为字节流以便存储或传输,然后再将字节流转换回对象。
- 自定义序列化 :通过实现
writeObject
和readObject
方法来自定义序列化过程。 - 版本控制 :使用
serialVersionUID
确保类的版本兼容性。 - 异常处理和资源管理:在进行文件操作和对象序列化时,必须处理可能出现的异常,并确保所有打开的流都被正确关闭。