java中的序列化(serialization)機制能夠將一個(gè)實(shí)例對象的狀態(tài)信息寫(xiě)入到一個(gè)字節流中,使其可以通過(guò)socket進(jìn)行傳輸、或者持久化存儲到數據庫或文件系統中;然后在需要的時(shí)候,可以根據字節流中的信息來(lái)重構一個(gè)相同的對象。序列化機制在java中有著(zhù)廣泛的應用,EJB、RMI等技術(shù)都是以此為基礎的。
正確使用序列化機制 一般而言,要使得一個(gè)類(lèi)可以序列化,只需簡(jiǎn)單實(shí)現
java.io.Serializable接口即可。該接口是一個(gè)標記式接口,它本身不包含任何內容,實(shí)現了該接口則表示這個(gè)類(lèi)準備支持序列化的功能。如下例定義了類(lèi)Person,并聲明其可以序列化。
- public class Person implements java.io.Serializable {}
public class Person implements java.io.Serializable {}序列化機制是通過(guò)
java.io.ObjectOutputStream類(lèi)和
java.io.ObjectInputStream類(lèi)來(lái)實(shí)現的。在序列化(serialize)一個(gè)對象的時(shí)候,會(huì )先實(shí)例化一個(gè)ObjectOutputStream對象,然后調用其writeObject()方法;在反序列化(deserialize)的時(shí)候,則會(huì )實(shí)例化一個(gè)ObjectInputStream對象,然后調用其readObject()方法。下例說(shuō)明了這一過(guò)程。
- public void serializeObject(){
- String fileName = "ser.out";
- FileOutputStream fos = new FileOutputStream(fileName);
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- oos.writeObject(new Person());
- oos.flush();
- }
-
- public void deserializeObject(){
- String fileName = "ser.out";
- FileInputStream fos = new FileInputStream(fileName);
- ObjectInputStream oos = new ObjectInputStream(fos);
- Person p = oos.readObject();
- }
public void serializeObject(){String fileName = "ser.out";FileOutputStream fos = new FileOutputStream(fileName);ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(new Person());oos.flush();}public void deserializeObject(){String fileName = "ser.out";FileInputStream fos = new FileInputStream(fileName);ObjectInputStream oos = new ObjectInputStream(fos);Person p = oos.readObject();}上例中我們對一個(gè)Person對象定義了序列化和反序列化的操作。但如果Person類(lèi)是不能序列化的話(huà),即對不能序列化的類(lèi)進(jìn)行序列化操作,則會(huì )拋出
java.io.NotSerializableException異常。
JVM中有一個(gè)預定義的序列化實(shí)現機制,即默認調用
ObjectOutputStream.defaultWriteObject() 和
ObjectInputStream.defaultReadObject() 來(lái)執行序列化操作。如果想自定義序列化的實(shí)現,則必須在聲明了可序列化的類(lèi)中實(shí)現
writeObject()和
readObject()方法。
幾種使用情況 一般在序列化一個(gè)類(lèi)A的時(shí)候,有以下三種情況:
[list=3]
類(lèi)A沒(méi)有父類(lèi),自己實(shí)現了Serializable接口類(lèi)A有父類(lèi)B,且父類(lèi)實(shí)現了Serializable接口類(lèi)A有父類(lèi)B,但父類(lèi)沒(méi)有實(shí)現Serializable接口 [/list]
對于第一種情況,直接實(shí)現Serializable接口即可。
對于第二種情況,因為父類(lèi)B已經(jīng)實(shí)現了Serializable接口,故類(lèi)A無(wú)需實(shí)現此接口;如果父類(lèi)實(shí)現了writeObject()和readObject(),則使用此方法,否則直接使用默認的機制。
對于第三種情況,則必須在類(lèi)A中顯示實(shí)現writeObject()和readObject()方法來(lái)處理父類(lèi)B的狀態(tài)信息;還有一點(diǎn)要特別注意,在父類(lèi)B中一定要有一個(gè)無(wú)參的構造函數,這是因為在反序列化的過(guò)程中并不會(huì )使用聲明為可序列化的類(lèi)A的任何構造函數,而是會(huì )調用其沒(méi)有申明為可序列化的父類(lèi)B的無(wú)參構造函數。
序列化機制的一些問(wèn)題
[list]性能問(wèn)題為了序列化類(lèi)A一個(gè)實(shí)例對象,所需保存的全部信息如下:
1. 與此實(shí)例對象相關(guān)的全部類(lèi)的元數據(metadata)信息;因為繼承關(guān)系,類(lèi)A的實(shí)例對象也是其任一父類(lèi)的對象。因而,需要將整個(gè)繼承鏈上的每一個(gè)類(lèi)的元數據信息,按照從父到子的順序依次保存起來(lái)。
2. 類(lèi)A的描述信息。此描述信息中可能包含有如下這些信息:類(lèi)的版本ID(version ID)、表示是否自定義了序列化實(shí)現機制的標志、可序列化的屬性的數目、每個(gè)屬性的名字和值、及其可序列化的父類(lèi)的描述信息。
3. 將實(shí)例對象作為其每一個(gè)超類(lèi)的實(shí)例對象,并將這些數據信息都保存起來(lái)。
在RMI等遠程調用的應用中,每調用一個(gè)方法,都需要傳遞如此多的信息量;久而久之,會(huì )對系統的性能照成很大的影響。版本信息當用readObject()方法讀取一個(gè)序列化對象的byte流信息時(shí),會(huì )從中得到所有相關(guān)類(lèi)的描述信息以及示例對象的狀態(tài)數據;然后將此描述信息與其本地要構造的類(lèi)的描述信息進(jìn)行比較,如果相同則會(huì )創(chuàng )建一個(gè)新的實(shí)例并恢復其狀態(tài),否則會(huì )拋出異常。這就是序列化對象的版本檢測。JVM中默認的描述信息是使用一個(gè)長(cháng)整型的哈希碼(hashcode)值來(lái)表示,這個(gè)值與類(lèi)的各個(gè)方面的信息有關(guān),如類(lèi)名、類(lèi)修飾符、所實(shí)現的接口名、方法和構造函數的信息、屬性的信息等。因而,一個(gè)類(lèi)作一些微小的變動(dòng)都有可能導致不同的哈希碼值。例如開(kāi)始對一個(gè)實(shí)例對象進(jìn)行了序列化,接著(zhù)對類(lèi)增加了一個(gè)方法,或者更改了某個(gè)屬性的名稱(chēng),當再想根據序列化信息來(lái)重構以前那個(gè)對象的時(shí)候,此時(shí)兩個(gè)類(lèi)的版本信息已經(jīng)不匹配,不可能再恢復此對象的狀態(tài)了。要解決這個(gè)問(wèn)題,可能在類(lèi)中顯示定義一個(gè)值,如下所示:
- private static final long serialVersionUID = ALongValue;
private static final long serialVersionUID = ALongValue;
這樣,序列化機制會(huì )使用這個(gè)值來(lái)作為類(lèi)的版本標識符,從而可以解決不兼容的問(wèn)題。但是它卻引入了一個(gè)新的問(wèn)題,即使一個(gè)類(lèi)作了實(shí)質(zhì)性的改變,如增加或刪除了一些可序列化的屬性,在這種機制下仍然會(huì )認為這兩個(gè)類(lèi)是相等的。
[/list]
一種更好的選擇
作為實(shí)現Serializable接口的一種替代方案,實(shí)現java.io.Externalizable接口同樣可以標識一個(gè)類(lèi)為可序列化。
Externalizable接口中定義了以下兩個(gè)方法:
- public void readExternal(ObjectInput in);
- public void writeExternal(ObjectOutput out);
public void readExternal(ObjectInput in);public void writeExternal(ObjectOutput out);
這兩個(gè)方法的功能與 readObject()和writeObject()方法相同,任何實(shí)現了Externalizable接口的類(lèi)都需要這實(shí)現兩個(gè)函數來(lái)定義其序列化機制。
使用Externalizable比使用Serializable有著(zhù)性能上的提高。前者序列化一個(gè)對象,所需保存的信息比后者要小,對于后者所需保存的第3個(gè)方面的信息,前者不需要訪(fǎng)問(wèn)每一個(gè)父類(lèi)并使其保存相關(guān)的狀態(tài)信息,而只需簡(jiǎn)單地調用類(lèi)中實(shí)現的writeExternal()方法即可。