Skip to content

序列化流

序列化流 是指将对象的状态(属性和数据)转换为字节流的机制。

它允许将对象转换为字节流,进行存储(如写入文件或数据库),序列化后的对象可以被保存或传输,然后在需要时再将其恢复为原始的对象格式。

序列化

序列化 是将对象转换为字节流的过程。使用 ObjectOutputStream 类。

构造函数:

构造函数描述
ObjectOutputStream(OutputStream out)创建序列化流

常用方法:

方法描述
writeObject(Object obj)将指定对象写入文件

提示

  1. 使用 ObjectOutputStream 序列化的类,必须实现 Serializable 接口!
  2. 如果类中的某个属性不想要序列化,则可以使用 transient 关键字修饰!
java
@Test
public void test() {
  Person person = new Person("王一博", 23);

  try (FileOutputStream fileOut = new FileOutputStream("E:\\StudyCode\\1.txt")) {
    ObjectOutputStream oos = new ObjectOutputStream(fileOut);
    // 将对象写入文件
    oos.writeObject(person);
  } catch (IOException e) {
    e.printStackTrace();
  }
}
java
public class Person implements Serializable {
  private String name;
  private transient Integer age;

  public Person() {
  }

  public Person(String name, Integer age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public Integer getAge() {
    return age;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return "Person{" +
      "name='" + name + '\'' +
      ", age=" + age +
      '}';
  }
}

反序列化

反序列化 是将字节流转换回对象的过程。使用 ObjectInputStream 类。

构造函数:

构造函数描述
ObjectInputStream(InputStream in)创建反序列化流

常用方法:

方法描述
readObject()读取数据并反序列化
java
@Test
public void test1() {
  try (FileInputStream fileIn = new FileInputStream("E:\\StudyCode\\1.txt")) {
    ObjectInputStream ois = new ObjectInputStream(fileIn);
    // 从文件中读取并反序列化
    Person p = (Person) ois.readObject();
    System.out.println(p);
  } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}

反序列化冲突问题

序列化运行时,每个可序列化类都会关联一个版本号,称为 serialVersionUID,在反序列化期间使用它来验证序列化对象的发送方和接收方对象是否相同,如果 serialVersionUID 不同,则会导致 InvalidClassException 报错。

就比如上面的 Person 类,如果序列化之后,改动了 Person 内部的属性,此时不再次 序列化的话,就会出现已序列化的 serialVersionUID 和当前的 serialVersionUID 不一致,导致报错。

解决方案:可以在 Person 类内声明 static final long 类型的 serialVersionUID ,全局使用一个版本号。

java
public class Person implements Serializable {
  // 固定序列化版本号
  @Serial
  private static final long serialVersionUID = 42L;

  private String name;
  private transient Integer age;

  public Person() {
  }

  // ...
}

多对象反序列化

如果想要序列化多个对象到文件中,并且再将其全部反序列化出来,该如何实现呢?

常规方法:

java
@Test
public void test() {
  Person p1 = new Person("王一博", 23);
  Person p2 = new Person("陈伟霆", 28);
  Person p3 = new Person("彭于晏", 35);

  try (FileOutputStream fos = new FileOutputStream("E:\\StudyCode\\1.txt")) {
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    // 每次写入一个 Person 对象
    oos.writeObject(p1);
    oos.writeObject(p2);
    oos.writeObject(p3);
  } catch (IOException e) {
    e.printStackTrace();
  }
}
java
@Test
public void test1() {
  try (FileInputStream fis = new FileInputStream("E:\\StudyCode\\1.txt")) {
    ObjectInputStream ois = new ObjectInputStream(fis);
    // 每次从文件中读取一个 Person 对象
    Person p1 = (Person) ois.readObject();
    Person p2 = (Person) ois.readObject();
    Person p3 = (Person) ois.readObject();
    System.out.println(p1);
    System.out.println(p2);
    System.out.println(p3);
  } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}
java
@Test
public void test2() {
  try (FileInputStream fis = new FileInputStream("E:\\StudyCode\\1.txt")) {
    ObjectInputStream ois = new ObjectInputStream(fis);

    // 思考:这里的 length = 3,怎么优化为存储的个数呢,而不是写死?
    for (int i = 0; i < 3; i++) {
      Person p = (Person) ois.readObject();
      System.out.println(p);
    }
  } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}

如果想要使用循环的方式,优化上面的代码,该怎么做呢?

方式一:使用 数组 将所有的对象合并,将对象序列化到文件中。

java
@Test
public void test3() {
  Person p1 = new Person("王一博", 23);
  Person p2 = new Person("陈伟霆", 28);
  Person p3 = new Person("彭于晏", 35);

  // 将3个对象合并为一个数组对象
  ArrayList<Person> list = new ArrayList<>();
  list.add(p1);
  list.add(p2);
  list.add(p3);

  try (FileOutputStream fos = new FileOutputStream("E:\\StudyCode\\1.txt")) {
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(list);
  } catch (IOException e) {
    throw new RuntimeException(e);
  }
}
java
@Test
public void test4() {
  try (FileInputStream fis = new FileInputStream("E:\\StudyCode\\1.txt")) {
    ObjectInputStream ois = new ObjectInputStream(fis);
    ArrayList<Person> p = (ArrayList<Person>) ois.readObject();
    for (Person person : p) {
      System.out.println(person);
    }
  } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
  }
}

方式二:使用 while(true) 死循环进行读取,当读取结束时,会抛出 EOFException 异常,此时结束 while 循环即可。

java
@Test
public void test5() {
  try (FileInputStream fis = new FileInputStream("E:\\StudyCode\\1.txt")) {
    ObjectInputStream ois = new ObjectInputStream(fis);

    while (true) {
      try {
        Object obj = ois.readObject();
        if (obj instanceof Person) {
          Person p = (Person) obj;
          System.out.println(p);
        }
      } catch (EOFException e) {
        break;
      }
    }

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

Released under the MIT License.