Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。 将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。 整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。 类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法

序列化流

序列化.png
ObjectOutputStream 类序列化一个对象,并将它发送到输出流。包含很多写方法来写各种数据类型,但是一个特别的方法例外:

  1. public final void writeObject(Object x) throws IOException

ObjectInputStream 类包含如下反序列化一个对象的方法:

public final Object readObject() throws IOException, 
                                 ClassNotFoundException

该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。

ObjectOutputStream序列化对象

ObjectOutputStream 类用来序列化一个对象,如下的 SerializeDemo 例子实例化了一个 Employee 对象,并将该对象序列化到一个文件中。

将Java对象的原始数据类型写出到文件,实现对象的持久存储 
java.io.ObjectOutputStream extends OutputStream

构造方法
ObjectOutputStream(OutputStream out):创建写入指定 OutputStream 的 ObjectOutputStream
    OutputStream out:字节输出流

特有的成员方法
void writeObject(Object obj) //将指定的对象写入 ObjectOutputStream

⚠️ 注意: 一个类的对象要想序列化成功,必须满足两个条件:

  • 该类必须实现 java.io.Serializable 接口
    • Serializable 是一个标记接口,不实现此接口的类将不会使任 何状态序列化或反序列化,会抛出 NotSerializableException
    • 如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。
  • 该类的所有属性必须是可序列化的
    • 如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰
    • 被static修饰的成员变量不能被序列化的
      FileOutputStream fileOut = new FileOutputStream("./employee.ser");
      ObjectOutputStream out = new ObjectOutputStream(fileOut);
      out.writeObject(e);
      out.close();
      fileOut.close();
      
      ```java import java.io.*;

public class SerializeDemo { public static void main(String[] args) { Employee e = new Employee(); Employee.wifi = “Aima”; e.SSN = 441424199909091234L; e.name = “Ken”; e.age = 18; e.address = “China,GungDong,GuangZhou”; e.number = 101; try { FileOutputStream fileOut = new FileOutputStream(“./employee.ser”); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(e); out.close(); fileOut.close(); System.out.printf(“Serialized data is saved in ./employee.ser”); } catch (IOException i) { i.printStackTrace(); } } }

class Employee implements java.io.Serializable { private static final long serialVersionUID = 1L; public transient long SSN; // transient顺态修饰成员,不会被序列化 public static String wifi; // 被static修饰的成员变量不能被序列化的,序列化的都是对象,静态优先于非静态加载到内存中 public String name; public String address; public int age; public int number;

public void mailCheck() { System.out.println(“Mailing a check to “ + name + “ “ + address); } }

该程序执行后,就创建了一个名为 employee.ser 文件。该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。<br />**⚠️ ****注意: **当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个.ser扩展名。

<a name="N9z4B"></a>
#### ObjectInputStream反序列化对象
```java
把文件中保存的对象,以流的方式读取出来使用
java.io.ObjectInputStream extends InputStream

构造方法
ObjectInputStream(InputStream in):创建从指定 InputStream 读取的 ObjectInputStream
      InputStream in:字节输入流

特有的成员方法:
Object readObject():从 ObjectInputStream 读取对象

⚠️注意: 反序列化的前提:

  • 类必须实现Serializable
  • 必须存在类对应的class文件

下面的 DeserializeDemo 程序实例了反序列化,./employee.ser存储了 Employee 对象。

FileInputStream fileIn = new FileInputStream("./employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
import java.io.*;

public class DeserializeDemo {
  public static void main(String[] args) {
    Employee e = null;
    try {
      FileInputStream fileIn = new FileInputStream("./employee.ser");
      ObjectInputStream in = new ObjectInputStream(fileIn);
      e = (Employee) in.readObject();
      in.close();
      fileIn.close();
    } catch (IOException i) {
      i.printStackTrace();
      return;
    } catch (ClassNotFoundException c) {
      System.out.println("Employee class not found");
      c.printStackTrace();
      return;
    }
    System.out.println("Deserialized Employee...");
    System.out.println("SSN: " + e.SSN);
    System.out.println("wift: " + Employee.wifi);
    System.out.println("Name: " + e.name);
    System.out.println("Address: " + e.address);
    System.out.println("Number: " + e.number);
  }
}

/*
以上程序编译运行结果如下所示:
SSN: 0
wift: null
Name: Ken
Address: China,GungDong,GuangZhou
Number: 101
*/

class Employee implements java.io.Serializable {
  private static final long serialVersionUID = 1L;
  public transient long SSN; // transient顺态修饰成员,不会被序列化
  public static String wifi; // 被static修饰的成员变量不能被序列化的,序列化的都是对象,静态优先于非静态加载到内存中
  public String name;
  public String address;
  public int age;
  public int number;

  public void mailCheck() {
    System.out.println("Mailing a check to " + name + " " + address);
  }
}

⚠️Tip:这里要注意以下要点:

  • readObject() 方法中的 try/catch代码块尝试捕获 ClassNotFoundException 异常。对于 JVM 可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException 异常。
  • 注意readObject() 方法的返回值被转化成 Employee 引用。当对象被序列化时,属性 SSN 的值为 111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后 Employee 对象的 SSN 属性为 0
  • Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序 列化的对象和对应类是否版本匹配
  • InvalidClassException 异常操作:当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException 异常。发生这个异常的原因如下
    • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
    • 该类包含未知数据类型
    • 该类没有可访问的无参数构造方法

打印流

平时我们在控制台打印输出,是调用System.out的 print 方法和 println 方法完成的,这两个方法都来自于 java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式

继承自父类的成员方法
PrintStream extends OutputStream
注意
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97

构造方法:
PrintStream(File file):输出的目的地是一个文件
PrintStream(OutputStream out):输出的目的地是一个字节输出流
PrintStream(String fileName):输出的目的地是一个文件路径
方法 说明
public void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流
public void write(byte[] b, int off, int len) 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public abstract void write(int b) 将指定的字节输出流
public void flush()
刷新此输出流并强制任何缓冲的输出字节被写出
public void close() 关闭此输出流并释放与此流相关联的任何系统资源

Printstream.png

import java.io.FileNotFoundException;
import java.io.PrintStream;

public class PrintDemo {
  public static void main(String[] args) throws FileNotFoundException {
    System.out.println("Hello Word");
    PrintStream ps = new PrintStream("./print.txt");
    ps.write(97);
    ps.println(97);
    ps.println(8.8);
    ps.println('a');
    ps.println("Hello Word");
    ps.println(true);
    ps.close();
  }
}

System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象, 我们就可以玩一个”小把戏”,改变它的流向

import java.io.*;

public class PrintDemo {
  public static void main(String[] args) throws FileNotFoundException {
    System.out.println("我是在控制台输出");
    PrintStream ps = new PrintStream("./目的打印流.txt");
    System.setOut(ps);
    System.out.println("我在【目的打印流】输出");
    ps.close();
  }
}