Java 序列化与反序列化终极解析

发布于:2025-04-20 ⋅ 阅读:(12) ⋅ 点赞:(0)

Java 序列化与反序列化终极解析

1. 核心概念

(1) 什么是序列化?

  • 定义:将对象转换为字节流的过程(对象 → 字节)

  • 目的

    • 持久化存储(如保存到文件)

    • 网络传输(如RPC调用)

    • 深拷贝实现

(2) 什么是反序列化?

  • 定义:将字节流还原为对象的过程(字节 → 对象)

  • 关键点

    • 不会调用构造方法

    • 字段值直接从字节流读取

Q:在java语言中是如何区分class版本的?

A:1.首先通过类的名字,然后在通过序列化版本号进行区分的

注意:在java语言中,不能仅仅通过一个类的名字来进行类的区分,这样太危险了。

2. 核心API

(1) 序列化相关类

类/接口 作用
Serializable 标记接口(无方法)
Externalizable 提供自定义序列化(需实现2个方法)
ObjectOutputStream 序列化输出流
ObjectInputStream 反序列化输入流

(2) 关键方法

java

// 序列化
objectOutputStream.writeObject(obj);

// 反序列化
Object obj = objectInputStream.readObject();

3. 完整流程解析

(1) 序列化流程

  1. 检查对象是否实现Serializable

  2. 递归序列化所有非transient字段

  3. 写入类描述信息(含serialVersionUID

  4. 写入字段数据

(2) 反序列化流程

  1. 读取并验证serialVersionUID

  2. 分配对象内存(不调用构造方法)

  3. 递归填充字段值

  4. 对于Externalizable对象,调用readExternal()

4. 代码示例

(1) 基础序列化

java

// 可序列化类
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age; // 不参与序列化
    
    // 构造方法、getter/setter省略
}

// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("person.dat"))) {
    oos.writeObject(new Person("Alice", 25));
}

// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("person.dat"))) {
    Person p = (Person) ois.readObject();
    System.out.println(p.getName()); // 输出Alice
    System.out.println(p.getAge()); // 输出0(transient字段)
}

(2) 自定义序列化(Externalizable)

java

class Student implements Externalizable {
    private String name;
    
    // 必须有无参构造器
    public Student() {}
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException {
        this.name = in.readUTF();
    }
}

5. 关键机制

(1) serialVersionUID:序列化版本号

  • 作用:版本控制,防止类结构变更导致反序列化失败

  • 生成方式

    • 显式声明:private static final long serialVersionUID = 1L;

    • 隐式生成:根据类结构计算hash值

(2) 序列化规则

字段类型 是否序列化 说明
普通实例字段
transient字段 如密码字段
static字段 属于类而非对象
未实现Serializable 抛出异常 所有引用字段必须可序列化

6. 高级特性

(1) 自定义序列化

java

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject(); // 默认序列化
    out.writeUTF(encrypt(password)); // 自定义处理
}

private void readObject(ObjectInputStream in) 
    throws IOException, ClassNotFoundException {
    in.defaultReadObject(); // 默认反序列化
    this.password = decrypt(in.readUTF()); // 自定义处理
}

(2) 序列化代理模式

java

private Object writeReplace() {
    return new SerializationProxy(this);
}

private static class SerializationProxy implements Serializable {
    private final String data;
    SerializationProxy(OriginalClass obj) {
        this.data = obj.getData();
    }
    
    private Object readResolve() {
        return new OriginalClass(data);
    }
}

7. 常见问题与解决方案

(1) 反序列化漏洞

  • 风险:攻击者可能构造恶意字节流执行任意代码

  • 防护

    • 使用ObjectInputFilter设置白名单

    java

ObjectInputFilter filter = info -> 
    info.serialClass() == Person.class ? Status.ALLOWED : Status.REJECTED;
ois.setObjectInputFilter(filter);

(2) 性能优化

  • 替代方案

    • JSON(Jackson/Gson)

    • 二进制协议(Protocol Buffers)

    • Kryo(高性能Java序列化)

8. 记忆技巧

(1) 核心口诀

"序列化要接口,transient能跳过
static不算数,UID保平安
构造方法不执行,字段直接写内存"

(2) 流程图解

复制

序列化:
[对象] → [检查Serializable] → [写入元数据] → [递归写字段] → [字节流]

反序列化:
[字节流] → [验证UID] → [分配内存] → [填充字段] → [返回对象]

9. 面试高频问题

  1. Q: 反序列化时如何避免调用构造方法?
    A: JVM直接分配内存并从字节流填充数据

  2. Q: 为什么Serializable是空接口?
    A: 标记接口,仅用于类型检查

  3. Q: 如何实现深拷贝?
    A: 序列化后再反序列化

  4. QExternalizableSerializable区别?
    A: 前者需实现方法,后者自动序列化

10. 最佳实践

  1. 安全性

    • 敏感字段标记transient

    • 使用ObjectInputFilter

  2. 版本控制

    • 始终显式声明serialVersionUID

  3. 性能

    • 避免序列化大对象

    • 考虑替代方案(如ProtoBuf)

  4. 代码健壮性

    • 实现readObject()时保持防御性编程

    • 对不可变对象使用序列化代理模式

通过这个完整体系,你已掌握Java序列化的:
✅ 核心机制 ✅ 安全风险 ✅ 性能优化 ✅ 工程实践


网站公告

今日签到

点亮在社区的每一天
去签到