序列化流和反序列化流---ObjectOutputStream/ObjectInputStream

不要被未知的东西吓到了,自从学完字节流和字符流之后,后面的所有流其实大部分都是二阶流,都是在这四种流上套了一层壳子;

在Java中,序列化(Serialization)是将对象转换为字节流的过程,以便存储或传输

反序列化(Deserialization)则是将字节流恢复为对象;

以下是详细的用法说明:


一、实现序列化的核心机制

  1. Serializable接口

不是所有的类都可以序列化,可以序列化的类必须实现java.io.Serializable接口(标记接口,无方法,也无需干什么):

public class User implements Serializable {
    private String name;
    private int age;
    // ... 构造方法、getter/setter等
}

就是写上实现了Serializable接口就可以了,也不需要重写啥方法,就是个标记;

  1. 序列化与反序列化方法

所谓的的序列化不过是以一个字节流为参数,造了一个序列化流,然后调用了这个序列化流的一个方法而已,仅此而已;

反序列化亦然;

// 序列化到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
    User user = new User("Alice", 30);
    oos.writeObject(user);
}

// 从文件反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
    User restoredUser = (User) ois.readObject();
}
    • 序列化对象:通过ObjectOutputStream.writeObject()写;
    • 反序列化对象:通过ObjectInputStream.readObject()读;

二、关键特性与用法

  1. transient关键字
    标记不需要序列化的字段:
private transient String password; // 不会被序列化
  1. serialVersionUID的作用
    当类标注实现Serializable接口后,如果类中没有显式声明序列号,编译后,编译器会为该类生成serialVersionUID字段并为其生成一个序列号,当对象序列化之后,会将数据和serialVersionUID字段值一同保存;但是一旦改动了类的源文件(如成员的权限符、添加新字段、删除字段等),类中没有显式声明serialVersionUID的值的话,再次编译后就会自动生成新的serialVersionUID字段值,那么之前已经序列化成功的对象再被反序列化之后就会对比serialVersionUID字段的值,一旦不相同就会报错,这就是“序列化版本冲突””;

因此,显式声明版本号,可以避免类结构变更导致的兼容性问题:

//固定的写法
private static final long serialVersionUID = 1L;
    • 未显式声明时,JVM会根据类结构自动生成,类改动后可能导致反序列化失败。

三、关于序列化号

在 Java 中,序列化(Serializable)是将对象状态转换为字节流的过程,反序列化则是将字节流恢复为对象。serialVersionUID 是用于版本控制的关键字段,确保序列化和反序列化的类版本兼容。

核心作用
  1. 版本一致性校验:反序列化时 JVM 会检查类的 serialVersionUID 是否与字节流中的一致。如果不一致,会抛出 InvalidClassException
  2. 显式声明避免隐式生成:若不显式定义 serialVersionUID,编译器会根据类结构自动生成一个。类结构的任何修改(如新增字段)都会改变隐式生成的 UID,导致反序列化失败。
代码示例
示例 1:未显式声明 serialVersionUID(隐式生成)即当原类改变时序列号会改变

import java.io.*;

class Person implements Serializable {
    String name;
    int age;
    // 未显式声明 serialVersionUID
}

public class SerializationDemo {
    public static void main(String[] args) {
        // 序列化对象到文件
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
            Person person = new Person();
            person.name = "Alice";
            person.age = 30;
            oos.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 修改 Person 类(例如新增一个字段)后尝试反序列化
        // 反序列化时会因 serialVersionUID 不匹配而抛出 InvalidClassException
    }
}
示例 2:显式声明 serialVersionUID(版本兼容)即正确处理上述问题的方式
import java.io.*;

class Student implements Serializable {
    private static final long serialVersionUID = 1L;  // 显式声明
    String name;
    int age;
}

public class SafeSerialization {
    public static void main(String[] args) {
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            Student student = new Student();
            student.name = "Bob";
            student.age = 25;
            oos.writeObject(student);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化(即使修改了 Student 类,只要 serialVersionUID 不变,仍可反序列化)
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student restoredStudent = (Student) ois.readObject();
            System.out.println("Name: " + restoredStudent.name);  // 输出 Bob
            System.out.println("Age: " + restoredStudent.age);   // 输出 25
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、完整示例

import java.io.*;

public class SerializationDemo {
    static class User implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private transient String password;

        public User(String name, String password) {
            this.name = name;
            this.password = password;
        }

        @Override
        public String toString() {
            return "User{name='" + name + "', password='" + password + "'}";
        }
    }

    public static void main(String[] args) {
        User user = new User("Alice", "secret");

        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
            User restoredUser = (User) ois.readObject();
            System.out.println(restoredUser); // password为null(transient字段)
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

当序列化多个对象后,readObject()一次就是读取一个,如果想要读取多个就用循环读;


通过上述机制,Java序列化提供了灵活的对象持久化与传输能力,因为对象信息要想在网络中传输,就必须字节化;但需谨慎处理版本兼容性和安全性问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值