1. 序列化

在前面IO流的学习中,我们使用流对象读取和写入的数据类型都是数字、字符串等类型。那么如果想将对象保存到文件中,以及使用流来读取文件中保存的对象时,就需要使用Java中的序列化和反序列化机制:

  • 序列化:用一个字节序列表示一个对象,该字节序列包含该对象的数据、类型和对象中存储的属性等信息,通过序列化可以持久化的保存对象
  • 反序列化:根据文件中保存的字节序列,重构对象,将保存的对象的数据、类型和对象中存储的属性等信息读取出来

image-20200504183252903.png

Java中使用ObjectOutputStreamObjectInputStream来实现对象的序列化和反序列化。

2. ObjectOutputStream

ObjectOutputStream将Java对象的原始数据类型写出到文件中,从而实现对象的持久化存储。它是java.io.OutputStream的子类,因此,可以使用父类中共性的成员方法:

  • public void close():关闭此输出流并释放与此相关的任何系统资源
  • public void flush():刷新此输出流并强制任何缓冲的输出字节被写入
  • public void write(byte[] b): 将b.length的字节从指定的字符数组写入此输出流
  • public void write(byte[] b, int off, int len): 从指定的字节数组写入len长度的字节,从偏移量off开始输出到此输出流中
  • public abstract void write(int b):将指定的字节输出流

它的构造方法有:

  • ObjectOutputStream(OutputStream out):创建写入指定OutputStream的ObjectOutputStream

它有一个特有的成员方法:

  • void writeObject(Object obj):将指定的对象写入ObjectOutputStream

使用步骤:

  • 创建ObjectOutputStream对象,构造方法中传递字节输出流
  • 使用ObjectOutputStream对象的writeObject()将对象写入到文件中
  • 释放资源
  1. public class Person implements Serializable {
  2. private int age;
  3. private String name;
  4. public Person() {
  5. }
  6. public Person(int age, String name) {
  7. this.age = age;
  8. this.name = name;
  9. }
  10. public int getAge() {
  11. return age;
  12. }
  13. public void setAge(int age) {
  14. this.age = age;
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. @Override
  23. public String toString() {
  24. return "Person{" +
  25. "age=" + age +
  26. ", name='" + name + '\'' +
  27. '}';
  28. }
  29. }
  1. import java.io.FileOutputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectOutputStream;
  4. public class ObjectOutputStreamTest {
  5. public static void main(String[] args) throws IOException {
  6. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\data\\Code\\Java_code\\src\\IOStream\\test.txt"));
  7. oos.writeObject(new Person(10, "Forlogen"));
  8. oos.close();
  9. }
  10. }

Serializable接口,也叫标记型接口,要进行序列化和反序列化的类必须实现Serializable接口,它会给类添加一个标记。

  1. public interface Serializable {
  2. }

当进行序列化和反序列化时,就会检测类上是否有此标记

  • 有则可正常序列化和反序列化
  • 没有则抛出NotSerializableException异常

3. ObjectInputStream

ObjectInputStream是对象的反序列化流,它是java.io.InputStream的子类,因此可以使用父类中共性成员方法:

  • int read():从输入流中读取数据的下一个字节
  • int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
  • void close():关闭此输入流并释放与该留相关的所有系统资源

构造方法:

  • ObjectInputStream(InputStream in):创建从指定InputStream读取的ObjectInputStream

特有的成员方法:

  • Object readObject():从ObjectInputStream中读取对象

使用步骤:

  • 创建ObjectInputStream对象,构造方法中传递字节输入流
  • 使用ObjectInputStream对象中的readObject()读取保存对象的文件
  • 释放资源
  • 使用读取出的对象

反序列化的前提:

  • 类必须实现Serializable接口接口
  • 必须存在类对应的.class文件
  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. public class ObjectInoutStreamTest {
  5. public static void main(String[] args) throws IOException, ClassNotFoundException {
  6. ObjectInputStream ois = new ObjectInputStream(
  7. new FileInputStream( "D:\\data\\Code\\Java_code\\src\\IOStream\\test.txt"));
  8. Object o = ois.readObject();
  9. ois.close();
  10. System.out.println(o); // Person{age=10, name='Forlogen'}
  11. }
  12. }

ClassNotFoundException会出现在反序列化的过程中,示意图如下所示: 序列化.png

为了避免异常的抛出,额可以显式的声明serialVersionUID,格式为:

  1. static final long serialVersionUID = 42L

4. transient关键字

transient关键字也称为瞬态关键字,它用于序列化和反序列化是进行关键字的屏蔽,被此关键字修饰的成员变量就不进行序列化操作;同时在反序列化时也会忽视被它修饰的存变量。例如,如果上面的Person类中的age属性被transient关键字修饰,那么就行序列化->反序列化的操作后,输出的信息就是Person{age=0, name="Forlogen"},此时age会保持默认值。