序列化是将对象转换为字节序列的过程,以便在网络上传输或持久化到存储介质中。而序列化流(ObjectInputStream 和 ObjectOutputStream)是 Java 中用于进行对象序列化和反序列化的工具类。
在 Java 中,主要使用 java.io.ObjectInputStream
和 java.io.ObjectOutputStream
类来实现序列化流的功能。
ObjectOutputStream
用于将对象序列化为字节流。
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; try (FileOutputStream fileOut = new FileOutputStream("object.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut)) { MyClass obj = new MyClass(); out.writeObject(obj); // 对象已成功序列化为字节流 } catch (IOException e) { e.printStackTrace(); }
- 在上述示例中,创建了一个
ObjectOutputStream
对象,并传入一个底层的FileOutputStream
作为字节输出流。然后,通过writeObject
方法将一个对象(MyClass
类的实例)写入到输出流中,从而将其序列化为字节流。 ObjectInputStream
用于将字节流反序列化为对象。
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; try (FileInputStream fileIn = new FileInputStream("object.ser"); ObjectInputStream in = new ObjectInputStream(fileIn)) { MyClass obj = (MyClass) in.readObject(); // 字节流已成功反序列化为对象 } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }
- 在上述示例中,创建了一个
ObjectInputStream
对象,并传入一个底层的FileInputStream
作为字节输入流。然后,通过readObject
方法从输入流中读取字节,并将其反序列化为对象(此处假设被读取的字节流确实表示一个MyClass
对象)。
当涉及到序列化流的细节时,有一些重要的方面需要注意:
- 实现
Serializable
接口:要使一个对象可以被序列化,它的类必须实现java.io.Serializable
接口。这是一个标记接口,没有定义任何方法。通过实现该接口,编译器将知道该类可以被序列化,并且可以将其对象转换为字节流。 - 序列化和反序列化的字段匹配:在进行序列化和反序列化时,对象的字段(即实例变量)将被写入和读取。确保被序列化和反序列化的对象具有相同的字段和字段顺序非常重要。如果序列化和反序列化的对象之间的字段不匹配,可能会导致数据损坏或异常。
- 版本控制:当序列化的类发生更改时,例如添加、删除或修改字段,建议为该类提供一个固定的版本号(
serialVersionUID
)。这样做可以在反序列化过程中提供版本控制,避免出现意外的问题。要自定义版本号,可以在类中添加以下代码:
private static final long serialVersionUID = 123456789L;
- 序列化多个对象:可以将多个对象连续地写入序列化流,然后再按照相同的顺序连续地从反序列化流中读取。这样可以将整个对象图保存到一个文件中,或者在网络上一次性传输多个对象。
- 序列化的限制:某些对象可能无法序列化。例如,如果对象中包含不可序列化的字段(如线程、文件句柄等),则会导致序列化失败。在这种情况下,可以通过将字段标记为
transient
来排除它们,使其不参与序列化过程。 - 序列化流的关闭:使用序列化流时,应该始终使用 try-with-resources 或手动关闭资源。序列化流中的缓冲区在关闭时会自动刷新并将剩余的数据写入到输出流中。
实例:
假设有一个学生类,用序列流写出一个学生
import java.io.Serializable; public class Student implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private String studentId; public Student(String name, int age, String studentId) { this.name = name; this.age = age; this.studentId = studentId; } // 省略了getter和setter方法 @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", studentId='" + studentId + '\'' + '}'; } }
接下来,我们将使用序列化流将一个学生对象写出到文件中。
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Main { public static void main(String[] args) { Student student = new Student("张三", 20, "20210001"); try (FileOutputStream fileOut = new FileOutputStream("student.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut)) { out.writeObject(student); System.out.println("学生对象已成功写出到文件"); } catch (IOException e) { e.printStackTrace(); } } }
以下是使用序列化流将学生对象写入idea的代码:
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Main { public static void main(String[] args) { try (FileInputStream fileIn = new FileInputStream("student.ser"); ObjectInputStream in = new ObjectInputStream(fileIn)) { Student student = (Student) in.readObject(); System.out.println(student); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }