034、对象流 (对象的序列化)

Par @Martin dans le
Tags :

Java 序列化与反序列化 (又叫串行化与反串行化)

Java 序列化是指把 Java 对象转换为字节序列的过程, 而 Java 反序列化是指把字节序列恢复为 Java 对象的过程.

序列化 API

java.io.ObjectOutputStream: 表示对象输出流

它的 writeObject(Object obj) 方法可以对参数指定的 obj 对象进行序列化, 把得到的字节序列写到一个目标输出流中.

java.io.ObjectInputStream: 表示对象输入流

它的 readObject() 方法源输入流中读取字节序列, 再把它们反序列化成为一个对象, 并将其返回.

实现序列化的要求

只有实现了 SerializableExternalizable 接口的类的对象才能被序列化, 否则抛出异常.

实现 Java 对象序列化与反序列化的方法

假定一个 Student 类, 它的对象需要序列化, 可以有如下三种方法:

方法一

若 Student 类仅仅实现了 Serializable 接口, 则可以按照以下方式进行序列化和反序列化.

ObjectOutputStream 采用默认的序列化方式, 对 Student 对象的非 transient 的实例变量进行序列化.

ObjcetInputStream 采用默认的反序列化方式, 对 Student 对象的非 transient 的实例变量进行反序列化.

transient 是 Java 语言的关键字, 用来表示一个域不是该对象串行化的一部分, 当一个对象被串行化的时候, transient 型变量的值不包括在串行化的表示中, 然而非 transient 型的变量是被包括进去的.

方法二

若 Student 类仅仅实现了 Serializable 接口, 并且还定义了 readObject(ObjectInputStream in)writeObject(ObjectOutputSteam out), 则采用以下方式进行序列化与反序列化.

ObjectOutputStream 会调用 Student 对象的 writeObject(ObjectOutputStream out) 的方法进行序列化.

ObjectInputStream 会调用 Student 对象的 readObject(ObjectInputStream in) 的方法进行反序列化.

方法三

若 Student 类实现了 Externalnalizable 接口, 且 Student 类必须实现 readExternal(ObjectInput in)writeExternal(ObjectOutput out) 方法, 则按照以下方式进行序列化与反序列化.

ObjectOutputStream 会调用 Student 对象的 writeExternal(ObjectOutput out) 的方法进行序列化.

ObjectInputStream 会调用 Student 对象的 readExternal(ObjectInput in) 的方法进行反序列化.

JDK 类库中序列化的步骤

步骤一:
创建一个对象输出流, 它可以包装一个其它类型的目标输出流, 如文件输出流:

ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream("D:\\objectfile.obj"));

步骤二:
通过对象输出流的 writeObject() 方法写对象:

out.writeObject("Hello");

out.writeObject(new Date());

JDK 类库中反序列化的步骤

步骤一:
创建一个对象输入流, 它可以包装一个其它类型输入流, 如文件输入流:

ObjectInputStream in = new ObjectInputStream(new fileInputStream("D:\\objectfile.obj"));

步骤二:
通过对象输出流的 readObject() 方法读取对象:

String obj1 = (String)in.readObject();

Date obj2 = (Date)in.readObject();

说明:
为了正确读取数据, 完成反序列化, 必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致.

类的版本

考虑一种情况, 假设定义了一个类, 实现了 Serializable, 并且序列化了这个类的实例, 如果这时, 改变了该类的结构, 那么反序列化就会报错, 提供类的版本 ID 不兼容.

所谓的版本 ID, 就是类的 private static final long serialVersionUID 变量, 如果没有定义这个 serialVersionUID, 类也会自动生成, 但只要改变了类的结构, serialVersionUID 也会跟着改变.

为此, 我们可以手动为类添加版本 ID: private static final long serialVersionUID = xxxxL;

实例

为了更好地理解 Java 序列化与反序列化, 选择方法一编码实现.

Student 类定义如下:

package com.jieke.io;
import java.io.Serializable;

/**
 *Title:学生类
 *Description:实现序列化接口的学生类
 *Copyright: copyright(c) 2012
 *Filename: Student.java
 *@author Wang Luqing
 *@version 1.0
 */
public class Student implements Serializable {
 private String name;
 private char sex;
 private int year;
 private double gpa;

 public Student() {

 }

 public Student(String name,char sex,int year,double gpa) {
  this.name = name;
  this.sex = sex;
  this.year = year;
  this.gpa = gpa;
 }

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

 public void setSex(char sex) {
  this.sex = sex;
 }

 public void setYear(int year) {
  this.year = year;
 }

 public void setGpa(double gpa) {
  this.gpa = gpa;
 }

 public String getName() {
  return this.name;
 }

 public char getSex() {
  return this.sex;
 }

 public int getYear() {
  return this.year;
 }

 public double getGpa() {
  return this.gpa;
 }
}


把 Student 类的对象序列化到文件 O:\\Java\\com\\jieke\\io\\student.txt, 并从该文件中反序列化, 向 console 显示结果. 代码如下:

import java.io.*;

/**
 *Title:应用学生类
 *Description:实现学生类实例的序列化与反序列化
 *Copyright: copyright(c) 2012
 *Filename: UseStudent.java
 *@author Wang Luqing
 *@version 1.0
 */

public class UseStudent {
 public static void main(String[] args) {
  Student st = new Student("Tom",'M',20,3.6);
  File file = new File("O:\\Java\\com\\jieke\\io\\student.txt");
  try {
   file.createNewFile();
  } catch(IOException e) {
   e.printStackTrace();
  }

  try {
   //Student对象序列化过程
   FileOutputStream fos = new FileOutputStream(file);
   ObjectOutputStream oos = new ObjectOutputStream(fos);
   oos.writeObject(st);
   oos.flush();
   oos.close();
   fos.close();

   //Student对象反序列化过程
   FileInputStream fis = new FileInputStream(file);
   ObjectInputStream ois = new ObjectInputStream(fis);
   Student st1 = (Student) ois.readObject();
   System.out.println("name = " + st1.getName());
   System.out.println("sex = " + st1.getSex());
   System.out.println("year = " + st1.getYear());
   System.out.println("gpa = " + st1.getGpa());
   ois.close();
   fis.close();
  } catch(ClassNotFoundException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}


结果如下所示:

name = Tom

sex = M

year = 20

gpa = 3.6


总结:

  • Java 序列化就是把对象转换成字节序列, 而 Java 反序列化就是把字节序列还原成 Java 对象.
  • 采用 Java 序列化与反序列化技术,
    • 一是可以实现数据的持久化, 在 MVC 模式中很是有用;
    • 二是可以对象数据的远程通信.