2445 字
12 分钟

Java序列化与反序列化详解

2026-02-02
浏览量 加载中...

Java序列化与反序列化详解#

什么是序列化与反序列化?#

序列化是将对象转换为字节序列的过程,便于存储或传输。反序列化是将字节序列转换回对象的过程。

序列化的应用场景#

  1. 对象持久化:将对象保存到文件或数据库中
  2. 网络传输:在网络中传输对象
  3. 进程间通信:在不同进程之间传递对象
  4. 分布式系统:在分布式系统中传递对象
  5. 缓存:将对象缓存到内存或磁盘中

序列化的实现方式#

Java中实现序列化的方式主要有两种:

1. 实现Serializable接口#

这是Java内置的序列化机制,也是最常用的方式。

2. 实现Externalizable接口#

这是一种更灵活的序列化机制,需要手动实现序列化和反序列化的逻辑。

Serializable接口#

基本使用#

import java.io.*;
// 实现Serializable接口
public class Person implements Serializable {
private String name;
private int age;
private transient String password; // transient关键字标记的字段不会被序列化
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
}
// 序列化和反序列化示例
public class SerializationDemo {
public static void main(String[] args) {
// 序列化对象
serializeObject();
// 反序列化对象
deserializeObject();
}
private static void serializeObject() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
Person person = new Person("张三", 25, "123456");
System.out.println("序列化前: " + person);
oos.writeObject(person);
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void deserializeObject() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("反序列化后: " + person);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

serialVersionUID#

serialVersionUID是序列化版本标识符,用于确保序列化和反序列化时使用相同版本的类。

public class Person implements Serializable {
// 显式声明serialVersionUID
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 其他代码
}

transient关键字#

transient关键字用于标记不需要序列化的字段。

private transient String password; // 密码字段不会被序列化

static字段#

静态字段不会被序列化,因为静态字段属于类,而不是对象。

private static String staticField; // 静态字段不会被序列化

Externalizable接口#

Externalizable接口继承自Serializable接口,需要手动实现序列化和反序列化的逻辑。

基本使用#

import java.io.*;
// 实现Externalizable接口
public class Person implements Externalizable {
private String name;
private int age;
private String password;
// 必须提供无参构造方法
public Person() {
}
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 手动序列化字段
out.writeUTF(name);
out.writeInt(age);
// 可以选择不序列化password字段
// out.writeUTF(password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 手动反序列化字段
name = in.readUTF();
age = in.readInt();
// 对应writeExternal的实现
// password = in.readUTF();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
}
// 序列化和反序列化示例
public class ExternalizableDemo {
public static void main(String[] args) {
// 序列化对象
serializeObject();
// 反序列化对象
deserializeObject();
}
private static void serializeObject() {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
Person person = new Person("张三", 25, "123456");
System.out.println("序列化前: " + person);
oos.writeObject(person);
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
}
private static void deserializeObject() {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("反序列化后: " + person);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

序列化的注意事项#

1. 版本兼容性#

  • 当类的结构发生变化时,可能会导致反序列化失败
  • 建议显式声明serialVersionUID,确保版本兼容性

2. 安全性#

  • 序列化可能会导致安全问题,如反序列化攻击
  • 不要序列化敏感信息,如密码
  • 可以使用transient关键字或Externalizable接口来控制序列化的内容

3. 性能#

  • 序列化和反序列化会影响性能
  • 对于大型对象,序列化和反序列化可能会很慢
  • 可以使用更高效的序列化框架,如Protobuf、Avro等

4. 循环引用#

  • Java的序列化机制可以处理循环引用
  • 但循环引用可能会导致序列化和反序列化的性能下降

5. 序列化的大小#

  • 序列化后的对象可能会很大,影响存储和传输
  • 可以使用压缩或更高效的序列化格式来减小大小

序列化的高级特性#

1. 自定义序列化#

可以通过实现writeObjectreadObject方法来自定义序列化和反序列化的逻辑。

public class Person implements Serializable {
private String name;
private int age;
private transient String password;
// 自定义序列化逻辑
private void writeObject(ObjectOutputStream out) throws IOException {
// 调用默认的序列化方法
out.defaultWriteObject();
// 自定义序列化逻辑
// 例如,对密码进行加密后序列化
String encryptedPassword = encrypt(password);
out.writeUTF(encryptedPassword);
}
// 自定义反序列化逻辑
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 调用默认的反序列化方法
in.defaultReadObject();
// 自定义反序列化逻辑
// 例如,对密码进行解密后赋值
String encryptedPassword = in.readUTF();
password = decrypt(encryptedPassword);
}
// 加密方法
private String encrypt(String password) {
// 简单的加密逻辑
return password + "_encrypted";
}
// 解密方法
private String decrypt(String encryptedPassword) {
// 简单的解密逻辑
return encryptedPassword.replace("_encrypted", "");
}
}

2. 序列化代理#

可以通过实现writeReplacereadResolve方法来使用序列化代理。

public class Person implements Serializable {
private String name;
private int age;
// 序列化代理
private static class SerializationProxy implements Serializable {
private String name;
private int age;
public SerializationProxy(Person person) {
this.name = person.name;
this.age = person.age;
}
// 反序列化时替换为原始对象
private Object readResolve() {
return new Person(name, age);
}
}
// 序列化时替换为代理对象
private Object writeReplace() {
return new SerializationProxy(this);
}
// 防止直接反序列化
private void readObject(ObjectInputStream in) throws InvalidObjectException {
throw new InvalidObjectException("Use SerializationProxy instead");
}
}

3. 序列化过滤器#

Java 9引入了序列化过滤器,可以防止反序列化攻击。

// 设置序列化过滤器
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"java.base/*;java.io/*;!*");
ObjectInputFilter.Config.setSerialFilter(filter);

序列化的替代方案#

1. JSON#

  • 轻量级的文本格式
  • 易于阅读和编写
  • 广泛支持各种编程语言
  • 常用的库:Jackson、Gson、Fastjson等

2. XML#

  • 结构化的文本格式
  • 支持复杂的数据结构
  • 广泛应用于配置文件和Web服务
  • 常用的库:JAXB、XStream等

3. Protobuf#

  • Google开发的二进制格式
  • 紧凑、高效
  • 支持版本控制
  • 适用于高性能场景

4. Avro#

  • Apache开发的二进制格式
  • 模式演进友好
  • 适用于大数据场景

5. MessagePack#

  • 高效的二进制格式
  • 比JSON小,比Protobuf简单
  • 适用于移动应用和游戏开发

序列化的最佳实践#

  1. 实现Serializable接口:对于需要序列化的类,实现Serializable接口

  2. 显式声明serialVersionUID:确保版本兼容性

  3. 使用transient关键字:标记不需要序列化的字段

  4. 自定义序列化逻辑:对于复杂对象,实现自定义的序列化逻辑

  5. 注意安全性:不要序列化敏感信息,如密码

  6. 考虑性能:对于大型对象,考虑使用更高效的序列化格式

  7. 处理循环引用:避免不必要的循环引用

  8. 测试序列化和反序列化:确保序列化和反序列化的正确性

  9. 文档化序列化行为:记录类的序列化行为,便于维护

  10. 使用序列化过滤器:防止反序列化攻击

常见陷阱#

  1. 忘记实现Serializable接口:导致序列化失败

  2. 版本不兼容:类的结构发生变化后,反序列化失败

  3. 序列化敏感信息:导致安全问题

  4. 循环引用:导致序列化和反序列化的性能下降

  5. 序列化大型对象:导致性能问题和内存溢出

  6. 使用默认的序列化实现:对于复杂对象,可能不是最优的

  7. 忽略transient关键字:导致不必要的字段被序列化

  8. 反序列化攻击:恶意构造的序列化数据可能导致安全问题

  9. 序列化的顺序:在使用Externalizable接口时,序列化和反序列化的顺序必须一致

  10. 序列化的大小:序列化后的对象可能会很大,影响存储和传输

总结#

序列化是Java编程中的重要特性,它允许我们将对象转换为字节序列,便于存储和传输。Java提供了内置的序列化机制,通过实现Serializable接口来实现。同时,也可以使用Externalizable接口来实现更灵活的序列化逻辑。

本文介绍了Java序列化的基本概念、实现方式、注意事项和最佳实践。希望本文能够帮助你更好地理解和使用Java的序列化机制。

练习#

  1. 编写一个程序,实现对象的序列化和反序列化。

  2. 编写一个程序,使用transient关键字标记不需要序列化的字段。

  3. 编写一个程序,实现自定义的序列化和反序列化逻辑。

  4. 编写一个程序,使用Externalizable接口实现序列化。

  5. 编写一个程序,演示序列化的版本兼容性问题。

  6. 编写一个程序,使用JSON格式实现对象的序列化和反序列化。

  7. 编写一个程序,测试序列化的性能。

  8. 编写一个程序,处理序列化中的循环引用。

  9. 编写一个程序,使用序列化代理来增强安全性。

  10. 编写一个程序,使用序列化过滤器来防止反序列化攻击。

通过这些练习,你将更加熟悉Java的序列化机制,为后续的学习做好准备。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Java序列化与反序列化详解
https://blog.vanilla.net.cn/posts/2026-02-05-java-serialization/
作者
鹁鸪
发布于
2026-02-02
许可协议
CC BY-NC-SA 4.0

评论区

目录