Java 序列化和反序列化

一、前言

        Java 对象序列化将对象的状态转换成一个字节流,并能够在以后将这个字节流完全恢复成原始对象的一个拷贝。如果类或其任何超类实现了java.io.Serializable接口或其子接口java.io.Externalizable,那么类的对象就是可序列化的。反序列化是将序列化后的对象恢复成原始对象的一个拷贝。

      序列化用于实现轻量级的持久化以及Java Remote Method Invocation(Java RMI)。持久化意味着对象的生命周期并不取决于程序是否正在执行,它可以存在于程序的调用之间,轻量级是相对于Hibernate这种重量级的持久化框架比较而言的。RMI使远程主机上的对象可以像在本地机器上一样操作,当向远程主机上的对象发送消息时,需要通过对象序列化来传输参数和返回值。

二、序列化和反序列化对象

       下面通过一个实际的例子来了解一下Java默认的序列化机制序列化和反序列化对象的步骤。

      代码清单-1

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author jackie
 *
 */
public class SerializableExample {
   static class Person implements Serializable {
   /**
    *
    */
   private static final long serialVersionUID = 6621617689818546522L;
   // 姓名
   private String name;
   // 年龄
   private Integer age;

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

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

   public String getName() {
     return name;
   }

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

   public Integer getAge() {
     return age;
   }

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

 /**
  * @param args
  */
 public static void main(String[] args) {
   Person orginal = new Person("test", 25);
   System.out.println(orginal);
   ObjectOutputStream oos = null;
   ObjectInputStream ois = null;
   try {
     // 序列化对象
     // 1、创建OutputStream对象
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     // 2、创建ObjectOutputStream对象,其构造函数参数是第一步创建的对象
     oos = new ObjectOutputStream(baos);
     // 3、写入对象
     oos.writeObject(orginal);
     oos.flush();
     oos.close();
     // 反序列化对象
     // 4、创建InputStream对象,其构造函数参数是第一步创建的OutputStream对象转换成的字节数组
     ByteArrayInputStream bais = new ByteArrayInputStream(
     baos.toByteArray());
     // 5、创建ObjectInputStream对象,其构造函数参数是第四步创建的对象
     ois = new ObjectInputStream(bais);
     // 6、读取对象
     Person deserialized = (Person) ois.readObject();
     ois.close();
     System.out.println(deserialized);
  } catch (IOException e) {
     e.printStackTrace();
  } catch (ClassNotFoundException e) {
     e.printStackTrace();
  } finally {
     try {
       if (oos != null) {
         oos.close();
       }
       if (ois != null) {
         ois.close();
       }
     } catch (IOException e) {
       e.printStackTrace();
     }
   }
  }
}

       输出结果如下:      

Person [name=test, age=25]
Person [name=test, age=25]

      说明:因为对象序列化是基于字节的,所以要使用InputStream和OutputStream继承层次结构。

      当序列化一个对象时,Java序列化机制会负责保存对象的整个“对象图”,它是恢复所保存对象所需一切内容的深拷贝。例如,如果对象A中包含对象B的一个引用,对象B中包含对象C的一个引用,那么当序列化对象A时,对象B和C都会被序列化,依次类推。     

三、Externalizable接口

   在早期的Java版本中, 反射的执行效率很低,因此序列化超大的对象图(如客户端-服务器模式下的RMI应用程序)存在很大的性能问题。 为了处理这种情况, Java提供了java.io.Externalizable接口, 它继承自java.io.Serializable接口,同时增加了两个方法:readExternal()和 writeExternal(),这两个方法会在序列化和反序列化的过程中被自动调用。Externalizable接口允许你定义自己的一套序列化机制,只需要在所需要序列化对象的类中实现readExternal()和 writeExternal()方法就行。这样就可以绕过反射的性能瓶颈。由于现在JVM的性能提升较大,反射不再是应用程序性能瓶颈,所以很难从Externalizable上获得性能优势。

      下面是使用Externalizable接口来实现对象序列化和反序列化。

      代码清单-2

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

class Person1 implements Externalizable {

  public Person1() {
    System.out.println("Person1 Constructed");
  }

  public void writeExternal(ObjectOutput out) throws IOException {
    System.out.println("Person1 writeExternal");
  }

  public void readExternal(ObjectInput in) throws IOException,
    ClassNotFoundException {
    System.out.println("Person1 readExternal");
  }
}

class Person2 implements Externalizable {

  Person2() {
    System.out.println("Person2 Constructed");
  }

  public void writeExternal(ObjectOutput out) throws IOException {
    System.out.println("Person2 writeExternal");
  }

  public void readExternal(ObjectInput in) throws IOException,
    ClassNotFoundException {
    System.out.println("Person2 readExternal");
  }
}

public class ExternalizableExample {

  public static void main(String[] args) throws IOException, ClassNotFoundException {
    Person1 person1 = new Person1();
    Person2 person2 = new Person2();
 
    // 序列化对象
    FileOutputStream fos = new FileOutputStream("Persons.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(person1);
    oos.writeObject(person2);
    oos.close();
 
    // 反序列化对象
    FileInputStream fis = new FileInputStream("Persons.out");
    ObjectInputStream ois = new ObjectInputStream(fis);
    person1 = (Person1) ois.readObject();
    person2 = (Person2) ois.readObject();
    ois.close();
  }
}
    输出结果如下:     

Person1 Constructed
Person2 Constructed
Person1 writeExternal
Person2 writeExternal
Person1 Constructed
Person1 readExternal
Exception in thread "main" java.io.InvalidClassException: com.codeproject.jackie.javadive.serialization.Person2; com.codeproject.jackie.javadive.serialization.Person2; no valid constructor
 at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:711)
 at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1749)
 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1327)
 at java.io.ObjectInputStream.readObject(ObjectInputStream.java:349)
 at com.codeproject.jackie.javadive.serialization.ExternalizableExample.main(ExternalizableExample.java:61)

       结果分析:对于Externalizable接口,对象反序列化使用的是public的无参构造函数来恢复对象,而Serializable使用的是从ObjectInputStream中读取的数据来恢复对象。由于Person2中的构造器不是public的,所以在恢复person2的过程中发生了异常。

      这也说明public的无参构造函数对于Externalizable对象是必须的,对于Seriablizable对象则不是。

四、Seriablizable接口Externalizable接口比较

       1) Serializable是一个标记接口(不包含任何方法),Externalizable接口包含writeExternal()和readExternal()这两个方法。

      2) 对于Externalizable接口,对象反序列化使用的是public的无参构造函数来恢复对象,而Serializable使用的是从ObjectInputStream中读取的数据来恢复对象 。

      3) 性能。除了通过使用transientstatic关键字来减少序列化属性的个数来提升默认序列化过程的性能,再没其它好办法了,但是使用Externalizable接口可以完全控制序列化过程(可能的速度提升)。

五、transient关键字

      如果我们不想让敏感的信息(密码)序列化,可以让类实现Externalizable接口,这样就可以在writeExternal()方法中只对需要序列化的属性进行显示的序列化。

      如果我们操作的是一个实现了Serializable接口的类的对象,就可以使用transient关键字来修饰不需要序列化的属性。

      代码清单-3

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author huangfeifei
 *
 */
public class Logon implements Serializable {

  private static final long serialVersionUID = -2753572193948315225L;
  private Date date = new Date();
  private String username;
  // password不会被序列化,因为使用了transient关键字
  private transient String password;

  public Logon(String username, String password) {
    this.username = username;
    this.password = password;
  }

  @Override
  public String toString() {
    return "Logon [date=" + date + ", username=" + username + ", password="
    + password + "]";
  }

  /**
   * @param args
   * @throws IOException
   * @throws FileNotFoundException
   * @throws InterruptedException
   * @throws ClassNotFoundException
   */
  public static void main(String[] args) throws FileNotFoundException,IOException, InterruptedException, ClassNotFoundException {
    Logon a = new Logon("jackie", "test");
    System.out.println("orginal :" + a);
    // 序列化
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Logon.out"));
    oos.writeObject(a);
    // 关闭流并延迟1秒
    oos.close();
    TimeUnit.SECONDS.sleep(1);
    // 反序列化
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Logon.out"));
    a = (Logon) ois.readObject();
    ois.close();
    System.out.println("deserialized :" + a);
  }
}

      输出结果如下:      

orginal :Logon [date=Wed Apr 02 22:23:52 CST 2014, username=jackie, password=test]
deserialized :Logon [date=Wed Apr 02 22:23:52 CST 2014, username=jackie, password=null]

      可见password属性没有被序列化。

      由于Externalizable对象默认情况下不保存任何属性,所以transient关键字只能和Serializable对象一起使用。

六、使用writeObject()和readObject()方法

   我们考虑这样一个问题:我们要序列化A对象,A对象有一个对象B的引用,但是B不能被序列化(transient),当反序列化A时,B是空的。如果我们想在反序列化A时,获得一个新的B,并且该B与序列化A时具有的B匹配,该怎么做呢?

      这个时候,我们可以在类中实现Serializable接口,并添加一组私有方法:writeObject()和readObject()。这样在序列化和反序列化过程中会自动调用这两个方法。它们是序列化系统提供的特殊回调契约的一部分,即“如果你的程序有一对与这个签名完全匹配的方法,就会在序列化和反序列化过程中调用它们”。writeObject()和readObject()方法必须具有与下面完全相同的签名: 

private void writeObject(ObjectOutputStream oos) throws IOException;
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException;

      先来看看下面的代码示例:

      代码清单-4  

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author jackie
 *
 */
public class Person implements Serializable {
  private static final long serialVersionUID = -3585689711395811197L;
  // 姓名
  private String name;
  // 年龄
  private transient Integer age;

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

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

  private void writeObject(ObjectOutputStream oos) throws IOException {
    oos.defaultWriteObject();
    oos.writeObject(age);
  }

  private void readObject(ObjectInputStream ois) throws IOException,
   ClassNotFoundException {
    ois.defaultReadObject();
    age = (Integer) ois.readObject();
  }

  /**
   * @param args
   * @throws IOException
   * @throws ClassNotFoundException
   */
  public static void main(String[] args) throws IOException,
   ClassNotFoundException {
    Person person = new Person("jackie", 20);
    System.out.println("orginal : " + person);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(person);
    oos.flush();
    oos.close();
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(
    baos.toByteArray()));
    Person person2 = (Person) ois.readObject();
    ois.close();
    System.out.println("deserialzed : " + person2);
  }
}
      输出结果如下:  
orginal : Person [name=jackie, age=20]
deserialzed : Person [name=jackie, age=20]
        如果想使用默认序列化机制写入对象的非transient部分,那么必须调用 defaultWriteObject()作为 writeObject()中的第一个操作,并让 defaultReadObject()作为 readObject()中的第一个操作。

七、参考资料  

       http://stackoverflow.com/questions/817853/what-is-the-difference-between-serializable-and-externalizable-in-java 

      《Thinking in Java》 



你可能感兴趣的:(Serializable,序列化,反序列化,externalizable)