java 序列化

来源:互联网 发布:上海科技大学考研 知乎 编辑:程序博客网 时间:2024/06/07 00:20

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

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

Java代码 

1.       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()方法即可。

原创粉丝点击