java序列化机制学习

什么是序列化
java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以根据字节流中的信息来重构一个相同的对象。序列化机制在java中有着广泛的应用,EJB、 RMI等技术都是以此为基础的。

正确使用序列化机制
一般而言,要使得一个类可以序列化,只需简单实现java.io.Serializable接口即可。该接口是一个标记式接口,它本身不包含任何内容,实现了该接口则表示这个类准备支持序列化的功能。如下例定义了类Person,并声明其可以序列化。

Java代码

   1. public class Person implements java.io.Serializable {}  

public class Person implements java.io.Serializable {}



序列化机制是通过java.io.ObjectOutputStream类和java.io.ObjectInputStream类来实现的。在序列化(serialize)一个对象的时候,会先实例化一个ObjectOutputStream对象,然后调用其 writeObject()方法;在反序列化(deserialize)的时候,则会实例化一个ObjectInputStream对象,然后调用其 readObject()方法。下例说明了这一过程。
Java代码

   1. public void serializeObject(){  
   2.      String fileName = "ser.out";  
   3.      FileOutputStream fos = new FileOutputStream(fileName);  
   4.      ObjectOutputStream oos = new ObjectOutputStream(fos);  
   5.      oos.writeObject(new Person());  
   6.      oos.flush();  
   7. }  
   8.   
   9. public void deserializeObject(){  
  10.      String fileName = "ser.out";  
  11.      FileInputStream fos = new FileInputStream(fileName);  
  12.      ObjectInputStream oos = new ObjectInputStream(fos);  
  13.      Person p = oos.readObject();  
  14. }  



上例中我们对一个Person对象定义了序列化和反序列化的操作。但如果Person类是不能序列化的话,即对不能序列化的类进行序列化操作,则会抛出 java.io.NotSerializableException异常。
JVM中有一个预定义的序列化实现机制,即默认调用 ObjectOutputStream.defaultWriteObject() 和 ObjectInputStream.defaultReadObject() 来执行序列化操作。如果想自定义序列化的实现,则必须在声明了可序列化的类中实现 writeObject()和readObject()方法。

几种使用情况
一般在序列化一个类A的时候,有以下三种情况:
# [list=3] 类A没有父类,自己实现了Serializable接口
# 类A有父类B,且父类实现了Serializable接口
# 类A有父类B,但父类没有实现Serializable接口
[/list]
对于第一种情况,直接实现Serializable接口即可。
对于第二种情况,因为父类B已经实现了Serializable接口,故类A无需实现此接口;如果父类实现了writeObject()和readObject(),则使用此方法,否则直接使用默认的机制。
对于第三种情况,则必须在类A中显示实现writeObject()和readObject()方法来处理父类B的状态信息;还有一点要特别注意,在父类B中一定要有一个无参的构造函数,这是因为在反序列化的过程中并不会使用声明为可序列化的类A的任何构造函数,而是会调用其没有申明为可序列化的父类B的无参构造函数。

序列化机制的一些问题
# [list] 性能问题
为了序列化类A一个实例对象,所需保存的全部信息如下:
1. 与此实例对象相关的全部类的元数据(metadata)信息;因为继承关系,类A的实例对象也是其任一父类的对象。因而,需要将整个继承链上的每一个类的元数据信息,按照从父到子的顺序依次保存起来。
2. 类A的描述信息。此描述信息中可能包含有如下这些信息:类的版本ID(version ID)、表示是否自定义了序列化实现机制的标志、可序列化的属性的数目、每个属性的名字和值、及其可序列化的父类的描述信息。
3. 将实例对象作为其每一个超类的实例对象,并将这些数据信息都保存起来。
# 在RMI等远程调用的应用中,每调用一个方法,都需要传递如此多的信息量;久而久之,会对系统的性能照成很大的影响。 版本信息
当用readObject()方法读取一个序列化对象的byte流信息时,会从中得到所有相关类的描述信息以及示例对象的状态数据;然后将此描述信息与其本地要构造的类的描述信息进行比较,如果相同则会创建一个新的实例并恢复其状态,否则会抛出异常。这就是序列化对象的版本检测。JVM中默认的描述信息是使用一个长整型的哈希码(hashcode)值来表示,这个值与类的各个方面的信息有关,如类名、类修饰符、所实现的接口名、方法和构造函数的信息、属性的信息等。因而,一个类作一些微小的变动都有可能导致不同的哈希码值。例如开始对一个实例对象进行了序列化,接着对类增加了一个方法,或者更改了某个属性的名称,当再想根据序列化信息来重构以前那个对象的时候,此时两个类的版本信息已经不匹配,不可能再恢复此对象的状态了。要解决这个问题,可能在类中显示定义一个值,如下所示:
Java代码

   1. private static final long serialVersionUID = ALongValue;  

这样,序列化机制会使用这个值来作为类的版本标识符,从而可以解决不兼容的问题。但是它却引入了一个新的问题,即使一个类作了实质性的改变,如增加或删除了一些可序列化的属性,在这种机制下仍然会认为这两个类是相等的。
[/list]
一种更好的选择
作为实现Serializable接口的一种替代方案,实现java.io.Externalizable接口同样可以标识一个类为可序列化。
Externalizable接口中定义了以下两个方法:
Java代码

   1. public void readExternal(ObjectInput in);  
   2. public void writeExternal(ObjectOutput out); 


这两个方法的功能与 readObject()和writeObject()方法相同,任何实现了Externalizable接口的类都需要这实现两个函数来定义其序列化机制。
使用Externalizable比使用Serializable有着性能上的提高。前者序列化一个对象,所需保存的信息比后者要小,对于后者所需保存的第3个方面的信息,前者不需要访问每一个父类并使其保存相关的状态信息,而只需简单地调用类中实现的writeExternal()方法即可。

你可能感兴趣的:(java,jvm,String,serialization,ejb,Class)