Java学习之——“IO流“的进阶流之序列化流的学习

发布于:2025-09-12 ⋅ 阅读:(22) ⋅ 点赞:(0)

一、核心概念:什么是序列化与反序列化?

  • 序列化 (Serialization): 将一个对象(在内存中的状态)转换成一个字节序列的过程。这个字节序列包含了对象的数据、对象的类型以及对象中存储的属性等信息。
  • 反序列化 (Deserialization): 将序列化后得到的字节序列恢复为一个对象的过程。

目的

  • 持久化存储:将对象永久地保存到硬盘上的文件中,下次程序启动时可以恢复。
  • 网络传输:将对象通过网络从一个节点传输到另一个节点,例如在 RPC(远程过程调用)、消息队列或分布式系统中。

二、序列化的实现

        Java 中要让一个类的对象能够被序列化,非常简单:只需要让这个类实现 java.io.Serializable 接口即可。Serializable 接口是一个标记接口(Marker Interface),它内部没有任何方法需要实现。它的作用仅仅是“标记”这个类的对象是可序列化的,告诉 Java 虚拟机(JVM):“请注意,我这个类的对象可以被序列化哦!”

示例:定义一个可序列化的类

import java.io.Serializable;

// 实现 Serializable 接口
public class Person implements Serializable {
    
    // 强烈建议显式声明 serialVersionUID
    private static final long serialVersionUID = 1L; 
    
    private String name;
    private int age;
    // transient 关键字标记的成员变量不会被序列化
    private transient String password; 

    // 构造方法、getter、setter 等...
    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 + '\'' + // 反序列化后 password 会是 null
                '}';
    }
    // ... 省略 getter 和 setter
}

关键点 1:serialVersionUID

  • 是什么:一个类的序列化版本号。用于在反序列化时验证序列化的对象和当前类的版本是否一致。
  • 为什么重要:如果你不显式声明,JVM 会根据类的结构自动生成一个。一旦类的结构发生改变(比如增加了一个字段),自动生成的 serialVersionUID 也会改变,这将导致反序列化失败,抛出 InvalidClassException
  • 最佳实践强烈建议显式声明一个固定的 serialVersionUID(如 1L)。这样即使类后期增加了字段,只是反序列化时新字段为默认值,而不会直接失败,保证了向后兼容性。

关键点 2:transient 关键字

  • 作用:用于修饰成员变量,表示该变量不参与序列化过程。
  • 使用场景:用于保护敏感信息(如密码、密钥等),或者存储一些没必要序列化的临时数据(如缓存、线程句柄等)。

三、序列化流与反序列化流的核心类

Java 提供了两个专门的流来处理对象的序列化和反序列化:

  • ObjectOutputStream: 序列化流,用于将对象写入字节输出流(如文件)。
  • ObjectInputStream: 反序列化流,用于从字节输入流(如文件)中读取并重建对象。

它们通常需要包裹在字节流(如 FileInputStream/FileOutputStream)之上,因为它们的底层操作仍然是字节。

序列化对象(写入文件)
import java.io.*;

public class SerializationDemo {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30, "secret123");

        // try-with-resources 确保流正确关闭
        try (// 1. 创建节点流(字节流),指向目标文件
             FileOutputStream fos = new FileOutputStream("person.dat");
             // 2. 创建处理流(序列化流),包裹节点流
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {

            // 3. 关键操作:将对象写入(序列化)到文件
            oos.writeObject(person);
            System.out.println("对象序列化成功!");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
反序列化对象(从文件读取)
import java.io.*;

public class DeserializationDemo {
    public static void main(String[] args) {
        Person person = null;

        try (// 1. 创建节点流(字节流),连接到源文件
             FileInputStream fis = new FileInputStream("person.dat");
             // 2. 创建处理流(反序列化流),包裹节点流
             ObjectInputStream ois = new ObjectInputStream(fis)) {

            // 3. 关键操作:读取(反序列化)字节流并重建对象
            // 需要强制类型转换
            person = (Person) ois.readObject();
            System.out.println("对象反序列化成功!");
            System.out.println(person); // 调用 toString 方法

        } catch (IOException | ClassNotFoundException e) { // 注意 ClassNotFoundException
            e.printStackTrace();
        }

        // 输出:Person{name='Alice', age=30, password='null'}
        // password 被 transient 修饰,所以反序列化后为 null(默认值)
    }
}

五、重要注意事项与特性

静态变量不会被序列化

  • 序列化是针对对象实例的,静态变量属于类,不属于任何单个对象,所以不会被序列化。

引用类型的成员变量也必须可序列化

  • 如果一个类有引用类型的成员变量(例如 Person 类里有一个 Address address 字段),那么这个引用类型(Address 类)也必须实现 Serializable 接口,否则整个序列化过程会失败,抛出 NotSerializableException

反序列化不会调用构造方法

  • 对象是通过从流中读取数据并直接赋值来重建的,构造方法不会被调用。

网站公告

今日签到

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