-
序列化是什么:
序列化就是將一個對象的狀態(tài)(各個屬性量)保存起來,然后在適當(dāng)?shù)臅r(shí)候再獲得。
序列化分為兩大部分:序列化和反序列化。序列化是這個過程的第一部分,將數(shù)據(jù)分解成字節(jié)流,以便存儲在文件中或在網(wǎng)絡(luò)上傳輸。反序列化就是打開字節(jié)流并重構(gòu)對象。對象序列化不僅要將基本數(shù)據(jù)類型轉(zhuǎn)換成字節(jié)表示,有時(shí)還要恢復(fù)數(shù)據(jù)。恢復(fù)數(shù)據(jù)要求有恢復(fù)數(shù)據(jù)的對象實(shí)例
序列化的什么特點(diǎn):
如果某個類能夠被序列化,其子類也可以被序列化。聲明為static和transient類型的成員數(shù)據(jù)不能被序列化。因?yàn)閟tatic代表類的狀態(tài), transient代表對象的臨時(shí)數(shù)據(jù)。
public interface Serializable (API5.0)
類通過實(shí)現(xiàn) java.io.Serializable 接口以啟用其序列化功能。未實(shí)現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化??尚蛄谢惖乃凶宇愋捅旧矶际强尚蛄谢摹P蛄谢涌跊]有方法或字段,僅用于標(biāo)識可序列化的語義。
要允許不可序列化類的子類型序列化,可以假定該子類型負(fù)責(zé)保存和還原超類型的公用 (public)、受保護(hù)的 (protected) 和(如果可訪問)包 (package) 字段的狀態(tài)。僅在子類型擴(kuò)展的類有一個可訪問的無參數(shù)構(gòu)造方法來初始化該類的狀態(tài)時(shí),才可以假定子類型有此責(zé)任。如果不是這種情況,則聲明一個類為可序列化類是錯誤的。該錯誤將在運(yùn)行時(shí)檢測到。
在反序列化過程中,將使用該類的公用或受保護(hù)的無參數(shù)構(gòu)造方法初始化不可序列化類的字段。可序列化的子類必須能夠訪問無參數(shù)的構(gòu)造方法??尚蛄谢宇惖淖侄螌脑摿髦羞€原。
當(dāng)遍歷一個圖形時(shí),可能會遇到不支持可序列化接口的對象。在此情況下,將拋出 NotSerializableException,并將標(biāo)識不可序列化對象的類。
在序列化和反序列化過程中需要特殊處理的類必須使用下列準(zhǔn)確簽名來實(shí)現(xiàn)特殊方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
writeObject 方法負(fù)責(zé)寫入特定類的對象的狀態(tài),以便相應(yīng)的 readObject 方法可以還原它。通過調(diào)用 out.defaultWriteObject 可以調(diào)用保存 Object 的字段的默認(rèn)機(jī)制。該方法本身不需要涉及屬于其超類或子類的狀態(tài)。狀態(tài)是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數(shù)據(jù)類型的方法將各個字段寫入 ObjectOutputStream 來保存的。
readObject 方法負(fù)責(zé)從流中讀取并還原類字段。它可以調(diào)用 in.defaultReadObject 來調(diào)用默認(rèn)機(jī)制,以還原對象的非靜態(tài)和非瞬態(tài)字段。defaultReadObject 方法使用流中的信息來分配流中通過當(dāng)前對象中相應(yīng)命名字段保存的對象的字段。這用于處理類發(fā)展后需要添加新字段的情形。該方法本身不需要涉及屬于其超類或子類的狀態(tài)。狀態(tài)是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數(shù)據(jù)類型的方法將各個字段寫入 ObjectOutputStream 來保存的。
將對象寫入流時(shí)需要指定要使用的替代對象的可序列化類,應(yīng)使用準(zhǔn)確的簽名來實(shí)現(xiàn)此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法將由序列化調(diào)用,前提是如果此方法存在,而且它可以通過被序列化對象的類中定義的一個方法訪問。因此,該方法可以擁有私有 (private)、受保護(hù)的 (protected) 和包私有 (package-private) 訪問。子類對此方法的訪問遵循 java 訪問規(guī)則。
在從流中讀取類的一個實(shí)例時(shí)需要指定替代的類應(yīng)使用的準(zhǔn)確簽名來實(shí)現(xiàn)此特殊方法。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循與 writeReplace 相同的調(diào)用規(guī)則和訪問規(guī)則。
序列化運(yùn)行時(shí)使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關(guān)聯(lián),該序列號在反序列化過程中用于驗(yàn)證序列化對象的發(fā)送者和接收者是否為該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應(yīng)的發(fā)送者的類的版本號不同,則反序列化將會導(dǎo)致 InvalidClassException
??尚蛄谢惪梢酝ㄟ^聲明名為 "serialVersionUID"
的字段(該字段必須是靜態(tài) (static)、最終 (final) 的 long
型字段)顯式聲明其自己的 serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化類未顯式聲明 serialVersionUID,則序列化運(yùn)行時(shí)將基于該類的各個方面計(jì)算該類的默認(rèn) serialVersionUID 值,如“Java(TM) 對象序列化規(guī)范”中所述。不過,強(qiáng)烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因計(jì)算默認(rèn)的 serialVersionUID 對類的詳細(xì)信息具有較高的敏感性,根據(jù)編譯器實(shí)現(xiàn)的不同可能千差萬別,這樣在反序列化過程中可能會導(dǎo)致意外的 InvalidClassException
。因此,為保證 serialVersionUID 值跨不同 java 編譯器實(shí)現(xiàn)的一致性,序列化類必須聲明一個明確的 serialVersionUID 值。還強(qiáng)烈建議使用 private
修改器顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應(yīng)用于立即聲明類 -- serialVersionUID 字段作為繼承成員沒有用處
實(shí)用意義:
一:對象序列化可以實(shí)現(xiàn)分布式對象。主要應(yīng)用例如:RMI要利用對象序列化運(yùn)行遠(yuǎn)程主機(jī)上的服務(wù),就像在本地機(jī)上運(yùn)行對象時(shí)一樣。
二:java對象序列化不僅保留一個對象的數(shù)據(jù),而且遞歸保存對象引用的每個對象的數(shù)據(jù)??梢詫⒄麄€對象層次寫入字節(jié)流中,可以保存在文件中或在網(wǎng)絡(luò)連接上傳遞。利用對象序列化可以進(jìn)行對象的“深復(fù)制”,即復(fù)制對象本身及引用的對象本身。序列化一個對象可能得到整個對象序列。
EP:
import java.io.Serializable;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
class MeTree implements Serializable {
private static final long serialVersionUID = 42L;
public MeTree left;
public MeTree right;
public int id;
public int level;
private static int count = 0 ;
public MeTree( int depth) {
id = count ++ ;
level = depth;
if (depth > 0 ) {
left = new MeTree(depth - 1 );
right = new MeTree(depth - 1 );
}
}
public void print( int levels) {
for ( int i = 0 ; i < level; i ++ ) {
System.out.print( " " );
System.out.println( " node " + id);
if (level <= levels && left != null )
left.print(levels);
if (level <= levels && right != null )
right.print(levels);
}
}
public static void main (String argv[]) {
try {
/**/ /* 創(chuàng)建一個文件寫入序列化樹。 */
FileOutputStream ostream = new FileOutputStream( " MeTree.tmp " );
/**/ /* 創(chuàng)建輸出流 */
ObjectOutputStream p = new ObjectOutputStream(ostream);
/**/ /* 創(chuàng)建一個二層的樹。 */
MeTree base = new MeTree( 2 );
p.writeObject(base); // 將樹寫入流中。
p.writeObject( " LiLy is 惠止南國 " );
p.flush();
ostream.close(); // 關(guān)閉文件。
/**/ /* 打開文件并設(shè)置成從中讀取對象。 */
FileInputStream istream = new FileInputStream( " MeTree.tmp " );
ObjectInputStream q = new ObjectInputStream(istream);
/**/ /* 讀取樹對象,以及所有子樹 */
MeTree new_MeTree = (MeTree)q.readObject();
new_MeTree.print( 2 ); // 打印出樹形結(jié)構(gòu)的最上面 2級
String name = (String)q.readObject();
System.out.println( " \n " + name);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
可以看到,在序列化的時(shí)候,writeObject與readObject之間的先后順序。readObject將最先write的object read出來。用數(shù)據(jù)結(jié)構(gòu)的術(shù)語來講就姑且稱之為先進(jìn)先出吧!
在序列化時(shí),有幾點(diǎn)要注意的:
1:當(dāng)一個對象被序列化時(shí),只保存對象的非靜態(tài)成員變量,不能保存任何的成員方法和靜態(tài)的成員變量。
?。玻喝绻粋€對象的成員變量是一個對象,那么這個對象的數(shù)據(jù)成員也會被保存。
3:如果一個可序列化的對象包含對某個不可序列化的對象的引用,那么整個序列化操作將會失敗,并且會拋出一個NotSerializableException。我們可以將這個引用標(biāo)記為transient,那么對象仍然可以序列化
還有我們對某個對象進(jìn)行序列化時(shí)候,往往對整個對象全部序列化了,比如說類里有些數(shù)據(jù)比較敏感,不希望序列化,一個方法可以用transient來標(biāo)識,另一個方法我們可以在類里重寫
可以通過指定關(guān)鍵transient使對象中的某個數(shù)據(jù)元素不被還原,這種方式常用于安全上的保護(hù)。比如對象中保存的密碼。
//**
transient 只能用在類的成員變量上,不能用在方法里.
transient 變量不能是final和static的
transient(臨時(shí))關(guān)鍵字
控制序列化過程時(shí),可能有一個特定的子對象不愿讓Java的序列化機(jī)制自動保存與恢復(fù)。一般地,若那個子對象包含了不想序列化的敏感信息(如密碼),就會面臨這種情況。即使那種信息在對象中具有“private”(私有)屬性,但一旦經(jīng)序列化處理,人們就可以通過讀取一個文件,或者攔截網(wǎng)絡(luò)傳輸?shù)玫剿?br>為防止對象的敏感部分被序列化,一個辦法是將自己的類實(shí)現(xiàn)為Externalizable,就象前面展示的那樣。這樣一來,沒有任何東西可以自動序列化,只能在writeExternal()明確序列化那些需要的部分。
然而,若操作的是一個Serializable對象,所有序列化操作都會自動進(jìn)行。為解決這個問題,可以用transient(臨時(shí))逐個字段地關(guān)閉序列化,它的意思是“不要麻煩你(指自動機(jī)制)保存或恢復(fù)它了——我會自己處理的”。
例如,假設(shè)一個Login對象包含了與一個特定的登錄會話有關(guān)的信息。校驗(yàn)登錄的合法性時(shí),一般都想將數(shù)據(jù)保存下來,但不包括密碼。為做到這一點(diǎn),最簡單的辦法是實(shí)現(xiàn)Serializable,并將password字段設(shè)為transient。
password被設(shè)為transient,所以不會自動保存到磁盤;另外,自動序列化機(jī)制也不會作恢復(fù)它的嘗試。
一旦對象恢復(fù)成原來的樣子,password字段就會變成null。注意必須用toString()檢查password是否為null,因?yàn)槿粲眠^載的“+”運(yùn)算符來裝配一個String對象,而且那個運(yùn)算符遇到一個null句柄,就會造成一個名為NullPointerException的違例(新版Java可能會提供避免這個問題的代碼)。
我們也發(fā)現(xiàn)date字段被保存到磁盤,并從磁盤恢復(fù),沒有重新生成。
由于Externalizable對象默認(rèn)時(shí)不保存它的任何字段,所以transient關(guān)鍵字只能伴隨Serializable使用。
**// 還有我們對某個對象進(jìn)行序列化時(shí)候,往往對整個對象全部序列化了,比如說類里有些數(shù)據(jù)比較敏感,不希望序列化,一個方法可以用transient來標(biāo)識,另一個方法我們可以在類里重寫
1、實(shí)現(xiàn)Serializable會導(dǎo)致發(fā)布的API難以更改,并且使得package-private和private 這兩個本來封裝的較好的咚咚也不能得到保障 2、Serializable會為每個類生成一個序列號,生成依據(jù)是類名、類實(shí)現(xiàn)的接口名、 public和protected方法,所以只要你一不小心改了一個已經(jīng)publish的API,并且沒有自 己定義一個long類型的叫做serialVersionUID的field,哪怕只是添加一個getXX,就會 讓你讀原來的序列化到文件中的東西讀不出來(不知道為什么要把方法名算進(jìn)去?) 3、不用構(gòu)造函數(shù)用Serializable就可以構(gòu)造對象,看起來不大合理,這被稱為 extralinguistic mechanism,所以當(dāng)實(shí)現(xiàn)Serializable時(shí)應(yīng)該注意維持構(gòu)造函數(shù)中所維 持的那些不變狀態(tài) 4、增加了發(fā)布新版本的類時(shí)的測試負(fù)擔(dān) 5、1.4版本后,JavaBeans的持久化采用基于XML的機(jī)制,不再需要Serializable6、設(shè)計(jì)用來被繼承的類時(shí),盡量不實(shí)現(xiàn)Serializable,用來被繼承的interface也不要 繼承Serializable。但是如果父類不實(shí)現(xiàn)Serializable接口,子類很難實(shí)現(xiàn)它,特別是 對于父類沒有可以訪問的不含參數(shù)的構(gòu)造函數(shù)的時(shí)候。所以,一旦你決定不實(shí)現(xiàn) Serializable接口并且類被用來繼承的時(shí)候記得提供一個無參數(shù)的構(gòu)造函數(shù) 7、內(nèi)部類還是不要實(shí)現(xiàn)Serializable好了,除非是static的,(偶也覺得內(nèi)部類不適合 用來干這類活的) 8、使用一個自定義的序列化方法
看看下面這個保存一個雙向鏈表的例子:
publicclass StringList implementsSerializable
{
?privateint size = 0;
?private Entry head = null;
?
?privatestaticclass Entry implements Serializable
?{
? String data;
? Entry next;
? Entry previous;
?}
?...//Remainder ommitted
}
這樣會導(dǎo)致鏈表的每個元素以及元素之間的關(guān)系(雙向鏈表之間的連接)
都保存下來,更好的方法是提供一個自定義的序列化如下: //String List with a resonable custom serialized form
class StringList implementsSerializable
{
? privatetransientint size = 0;?????? //!transient
? privatetransient Entry head = null;? //!transient
?
? //no longer serializable!
? privatestaticclass Entry
? {
??? String data;
??? Entry next;
??? Entry previous;
? }
?
? //Appends the specified string to the list
? publicvoid add(String s) {/*...*/};
?
? /**
?? * Serialize thisStringList
instance
?? * @author yuchifang
?? * @serialData The size of the list (the number of strings
* it contains) is emitted(int), in the proper sequence
?? */
? privatevoid writeObject(ObjectOutputStream s)
throws IOException
? {
??? s.defaultWriteObject();
??? s.writeInt(size);
??? //Write out all elements in the proper order
??? for (Entry e = head; e != null; e = e.next)
????? s.writeObject(e.data);
? }
?
? privatevoid readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
? {
??? int numElements = s.readInt();
???
??? //Read in all elements andd insert them in list
??? for (int i = 0; i < numElements; i++)
????? add((String)s.readObject());
? }
? //...remainder omitted
}
9、不管你選擇什么序列化形式,聲明一個顯式的UID: private static final long serialVersionUID = randomLongValue;10、不需要序列化的東西使用transient注掉它吧,別什么都留著 11、writeObject/readObject重載以完成更好的序列化 readResolve 與 writeReplace重載以完成更好的維護(hù)invariant controllers
完全定制序列化過程:
如果一個類要完全負(fù)責(zé)自己的序列化,則實(shí)現(xiàn)Externalizable接口而不是Serializable接口。Externalizable接口定義包括兩個方法writeExternal()與readExternal()。利用這些方法可以控制對象數(shù)據(jù)成員如何寫入字節(jié)流.類實(shí)現(xiàn)Externalizable時(shí),頭寫入對象流中,然后類完全負(fù)責(zé)序列化和恢復(fù)數(shù)據(jù)成員,除了頭以外,根本沒有自動序列化。這里要注意了。聲明類實(shí)現(xiàn)Externalizable接口會有重大的安全風(fēng)險(xiǎn)。writeExternal()與readExternal()方法聲明為public,惡意類可以用這些方法讀取和寫入對象數(shù)據(jù)。如果對象包含敏感信息,則要格外小心。這包括使用安全套接或加密整個字節(jié)流。到此為至,我們學(xué)習(xí)了序列化的基礎(chǔ)部分知識。關(guān)于序
列化的高級教程,以后再述。