序列化用于实现轻量级的持久化以及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都会被序列化,依次类推。
在早期的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对象则不是。
1) Serializable是一个标记接口(不包含任何方法),Externalizable接口包含writeExternal()和readExternal()这两个方法。
2) 对于Externalizable接口,对象反序列化使用的是public的无参构造函数来恢复对象,而Serializable使用的是从ObjectInputStream中读取的数据来恢复对象 。
3) 性能。除了通过使用transient和static关键字来减少序列化属性的个数来提升默认序列化过程的性能,再没其它好办法了,但是使用Externalizable接口可以完全控制序列化过程(可能的速度提升)。
如果我们不想让敏感的信息(密码)序列化,可以让类实现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对象一起使用。
我们考虑这样一个问题:我们要序列化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》