天天看點

對象的序列化與反序列化

1.  簡介

對象序列化(serializable)是指将對象轉換為位元組序列的過程,而反序列化則是根據位元組序列恢複對象的過程。

序列化一般用于以下場景:

1.永久性儲存對象,儲存對象的位元組序列到本地檔案中; 

2.通過序列化對象在網絡中傳遞對象; 

3.通過序列化在程序間傳遞對象。 

對象所屬的類必須實作serializable或是externalizable接口才能被序列化。對實作了serializable接口的類,其序列化與反序列化采用預設的序列化方式,externalizable接口是繼承了serializable接口的接口,是對serializable的擴充,實作了externalizable接口的類完全自己控制序列化與反序列化行為。

java.io.objectoutputstream代表對象輸出流,其方法writeobject(object obj)可以實作對象的序列化,将得到的位元組序列寫到目标輸出流中。java.io.objectinputstream代表對象輸入流,其readobject()方法能從源輸入流中讀取位元組序列,将其反序列化為對象,并将其傳回。

2.  序列化的幾種方式

假設定義了一個customer類,根據customer實作序列化方式的不同,可能有以下幾種序列化方式:

1    

2    

2.1   實作serializable,未定義readobject和writeobject方法

objectoutputstream使用jdk預設方式對customer對象的非transient的執行個體變量進行序列化;

objectinputstream使用jdk預設方式對customer對象的非transient的執行個體變量進行反序列化。

2.2   實作serializable,并定義了readobject和writeobject方法

objectoutputstream調用customer類的writeobject(objectoutputstream out)方法對customer對象的非transient的執行個體變量進行序列化;

objectinputstream調用customer類的readobject(objectinputstream in)方法對customer對象的非transient的執行個體變量進行反序列化。

2.3   實作externalizable,定義readexternal和writeexternal方法

objectoutputstream調用customer類的writeexternal方法對customer對象的非transient執行個體變量進行序列化;

objectinputstream首先通過customer類的無參數構造函數執行個體化一個對象,再用readexternal方法對customer對象的非transient執行個體變量進行反序列化。

3.  serializable接口 

類通過實作 java.io.serializable 接口以啟用其序列化功能。未實作此接口的類将無法使其任何狀态序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于辨別可序列化的語義。

在反序列化過程中,将使用該類的公用或受保護的無參數構造方法初始化不可序列化類的字段。可序列化的子類必須能夠通路無參數構造方法。可序列化子類的字段将從該流中恢複。

當周遊一個類視圖時,可能會遇到不支援 serializable 接口的對象。在此情況下,将抛出 notserializableexception,并将辨別不可序列化對象的類。 

3.1  準确簽名

在序列化和反序列化過程中需要特殊處理的類必須使用下列準确簽名來實作特殊方法:

private void writeobject(java.io.objectoutputstream out) throws ioexception;

private void readobject(java.io.objectinputstream in) throws ioexception, classnotfoundexception;

private void readobjectnodata() throws objectstreamexception;

writeobject方法負責寫入特定類的對象的狀态,以便相應的 readobject方法可以恢複它。通過調用out.defaultwriteobject可以調用儲存object的字段的預設機制。該方法本身不需要涉及屬于其超類或子類的狀态。通過使用writeobject方法或使用 dataoutput 支援的用于基本資料類型的方法将各個字段寫入 objectoutputstream,狀态可以被儲存。

readobject 方法負責從流中讀取并恢複類字段。它可以調用 in.defaultreadobject 來調用預設機制,以恢複對象的非靜态和非瞬态字段。defaultreadobject 方法使用流中的資訊來配置設定流中通過目前對象中相應指定字段儲存的對象的字段。這用于處理類演化後需要添加新字段的情形。該方法本身不需要涉及屬于其超類或 子類的狀态。通過使用 writeobject 方法或使用 dataoutput 支援的用于基本資料類型的方法将各個字段寫入 objectoutputstream,狀态可以被儲存。

在序列化流不列出給定類作為将被反序列化對象的超類的情況下,readobjectnodata 方法負責初始化特定類的對象狀态。這在接收方使用的反序列化執行個體類的版本不同于發送方,并且接收者版本擴充的類不是發送者版本擴充的類時發生。在序列化流已經被篡改時也将發生;是以,不管源流是“敵意的”還是不完整的,readobjectnodata方法都可以用來正确地初始化反序列化的對象。

将對象寫入流時需要指定要使用的替代對象的可序列化類,應使用準确的簽名來實作此特殊方法:

any-access-modifier object writereplace() throws objectstreamexception;

此writereplace 方法将由序列化調用,前提是如果此方法存在,而且它可以通過被序列化對象的類中定義的一個方法通路。是以,該方法可以擁有私有 (private)、受保護的(protected)和包私有 (package-private)通路。子類對此方法的通路遵循java通路規則。

在從流中讀取類的一個執行個體時需要指定替代的類應使用的準确簽名來實作此特殊方法:

any-access-modifier object readresolve() throws objectstreamexception;

此 readresolve 方法遵循與 writereplace 相同的調用規則和通路規則。如果一個類定義了readresolve方法,那麼在反序列化的最後将調用readresolve方法,該方法傳回的對象為反序列化的最終結果。

3.2  serialversionuid

序列化運作時使用一個稱為serialversionuid的版本号與每個可序列化類相關聯,該序列号在反序列化過程中用于驗證序列化對象的發送者和接收者是否為該對象加載了與序列化相容的類。如果接收者加載的該對象的類的serialversionuid與對應的發送者的類的版本号不同,則反序列化将會導緻invalidclassexception。可序列化類可以通過聲明名為 "serialversionuid"的字段(該字段必須是靜态 (static)、最終 (final) 的 long 型字段)顯式聲明其自己的serialversionuid:any-access-modifier static final long serialversionuid = 42l;

如果可序列化類未顯式聲明serialversionuid,則序列化運作時将基于該類的各個方面計算該類的預設serialversionuid值,如“java(tm)對象序列化規範”中所述。不過,強烈建議所有可序列化類都顯式聲明serialversionuid值,原因是計算預設的serialversionuid對類的詳細資訊具有較高的敏感性,根據編譯器實作的不同可能千差萬别,這樣在反序列化過程中可能會導緻意外的invalidclassexception。是以,為保證serialversionuid值跨不同java編譯器實作的一緻性,序列化類必須聲明一個明确的serialversionuid值。還強烈建議使用private修飾符顯示聲明serialversionuid(如果可能),原因是這種聲明僅應用于直接聲明類--serialversionuid字段作為繼承成員沒有用處。數組類不能聲明一個明确的serialversionuid,是以它們總是具有預設的計算值,但是數組類沒有比對serialversionuid值的要求。

4.  externalizable接口

externalizable是serailizable的擴充,實作externalizable接口的類其序列化有以下特點:

序列化時調用類的方法writeexternal,反序列化調用readexternal方法;

在執行反序列化時先調用類的無參數構造函數,這一點與預設的反序列化是不同的,是以對實作externalizable接口來實作序列化的類而言,必須提供一個public的無參數構造函數,否則在反序列化時将出現異常。

5.  總結

如果采用預設的序列化方式,隻要讓一個類實作serializable接口,其執行個體就可以被序列化。通常,專門為繼承而設計的類應該盡量不要實作 serializable接口,因為一旦父類實作了serializable接口,其所有子類也都是可序列化的了。

預設的序列化方式的不足之處: 

1.直接對對象的不宜對外公開的敏感資料進行序列化,這是不安全的; 

2.不會檢查對象的成員變量是否符合正确的限制條件,有可能被傳改資料而導緻運作異常;

3.需要對對象圖做遞歸周遊,如果對象圖很複雜,會消耗很多資源,設定引起java虛拟機的堆棧溢出;

4.使類的接口被類的内部實作限制,制約類的更新與維護。

通過實作serializable接口的private類型的writeobject()和readobject(),或是實作 externalizable接口,并實作writeexternal()與readexternal()方法,并提供public類型的無參數構造函數 兩種方式來控制序列化過程可以有效規避預設序列化方式的不足之處。

繼續閱讀