序列化和反序列化

JAVA序列化機制的深入研究

對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整性和可傳遞性。

序列化算法一般會按步驟做如下事情:

◆ 將對象實例相關(guān)的類元數(shù)據(jù)輸出。

◆ 遞歸地輸出類的超類描述直到不再有超類。

◆ 類元數(shù)據(jù)完了以后,開始從最頂層的超類開始輸出對象實例的實際數(shù)據(jù)值。

◆ 從上至下遞歸輸出實例的數(shù)據(jù)

序列化及反序列化介紹

序列化是指把對象轉(zhuǎn)換成有序字節(jié)流,以便在網(wǎng)絡(luò)上傳輸或者保存在本地文件中。序列化后的字節(jié)流保存了Java對象的狀態(tài)以及相關(guān)的描述信息。客戶端從文件中或網(wǎng)絡(luò)上獲得序列化后的對象字節(jié)流后,根據(jù)字節(jié)流中所保存的對象狀態(tài)及描述信息,通過反序列化重建對象。本質(zhì) 上講,序列化就是把實體對象狀態(tài)按照一定的格式寫入到有序字節(jié)流,反序列化就是從有序字節(jié)流重建對象,恢復(fù)對象狀態(tài)。序列化機制的核心作用就是對象狀態(tài)的 保存與重建

2、什么情況下需要序列化

a)當(dāng)你想把的內(nèi)存中的對象狀態(tài)保存到一個文件中或者數(shù)據(jù)庫中時候;

b)當(dāng)你想用套接字在網(wǎng)絡(luò)上傳送對象的時候;

c)當(dāng)你想通過RMI傳輸對象的時候;

3、當(dāng)對一個對象實現(xiàn)序列化時,究竟發(fā)生了什么?

在沒有序列化前,每個保存在堆(Heap)中的對象都有相應(yīng)的狀態(tài)(state),即實例變量(instance ariable)

6、相關(guān)注意事項

a)序列化時,只對對象的狀態(tài)進行保存,而不管對象的方法;

b)當(dāng)一個父類實現(xiàn)序列化,子類自動實現(xiàn)序列化,不需要顯式實現(xiàn)Serializable接口;

c)當(dāng)一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化;

d)并非所有的對象都可以序列化,至于為什么不可以,有很多原因了,比如:

1.安全方面的原因,比如一個對象擁有private,public等field,對于一個要傳輸?shù)膶ο螅热鐚懙轿募蛘哌M行rmi傳輸?shù)鹊龋谛蛄谢M行傳輸?shù)倪^程中,這個對象的private等域是不受保護的。

2. 資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者保存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現(xiàn)。

聲明為static和transient類型的成員數(shù)據(jù)不能被序列化。因為static代表類的狀態(tài),transient代表對象的臨時數(shù)據(jù)

什么時候使用序列化:

一:對象序列化可以實現(xiàn)分布式對象。主要應(yīng)用例如:rmi要利用對象序列化運行遠程主機上的服務(wù),就像在本地機上運行對象時一樣。

二:java對象序列化不僅保留一個對象的數(shù)據(jù),而且遞歸保存對象引用的每個對象的數(shù)據(jù)。可以將整個對象層次寫入字節(jié)流中,可以保存在文件中或在網(wǎng)絡(luò) 連接上傳遞。利用對象序列化可以進行對象的"深復(fù)制",即復(fù)制對象本身及引用的對象本身。序列化一個對象可能得到整個對象序列。

類通過實現(xiàn)java.io.serializable接口以啟用其序列化功能。未實現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于標識可序列化的語義。

要允許不可序列化類的子類型序列化,可以假定該子類型負責(zé)保存和還原超類型的公用(public)、受保護的(protected)和(如果可訪問) 包(package)字段的狀態(tài)。僅在子類型擴展的類有一個可訪問的無參數(shù)構(gòu)造方法來初始化該類的狀態(tài)時,才可以假定子類型有此責(zé)任。如果不是這種情況, 則聲明一個類為可序列化類是錯誤的。該錯誤將在運行時檢測到。

在反序列化過程中,將使用該類的公用或受保護的無參數(shù)構(gòu)造方法初始化不可序列化類的字段。可序列化的子類必須能夠訪問無參數(shù)的構(gòu)造方法。可序列化子類的字段將從該流中還原

當(dāng)遍歷一個圖形時,可能會遇到不支持可序列化接口的對象。在此情況下,將拋出notserializableexception,并將標識不可序列化對象的類

在序列化和反序列化過程中需要特殊處理的類必須使用下列準確簽名來實現(xiàn)特殊方法:

writeobject方法負責(zé)寫入特定類的對象的狀態(tài),以便相應(yīng)的readobject方法可以還原它。通過調(diào)用 out.defaultwriteobject可以調(diào)用保存object的字段的默認機制。該方法本身不需要涉及屬于其超類或子類的狀態(tài)。狀態(tài)是通過使用 writeobject方法或使用dataoutput支持的用于基本數(shù)據(jù)類型的方法將各個字段寫入objectoutputstream來保存的。

readobject方法負責(zé)從流中讀取并還原類字段。它可以調(diào)用in.defaultreadobject來調(diào)用默認機制,以還原對象的非靜態(tài)和非 瞬態(tài)字段。defaultreadobject方法使用流中的信息來分配流中通過當(dāng)前對象中相應(yīng)命名字段保存的對象的字段。這用于處理類發(fā)展后需要添加新 字段的情形。該方法本身不需要涉及屬于其超類或子類的狀態(tài)。狀態(tài)是通過使用writeobject方法或使用dataoutput支持的用于基本數(shù)據(jù)類型 的方法將各個字段寫入objectoutputstream來保存的。

將對象寫入流時需要指定要使用的替代對象的可序列化類,應(yīng)使用準確的簽名來實現(xiàn)此特殊方法:

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

在從流中讀取類的一個實例時需要指定替代的類應(yīng)使用的準確簽名來實現(xiàn)此特殊方法。

序列化運行時使用一個稱為serialversionuid的版本號與每個可序列化類相關(guān)聯(lián),該序列號在反序列化過程中用于驗證序列化對象的發(fā)送者和 接收者是否為該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的serialversionuid與對應(yīng)的發(fā)送者的類的版本號不同,則反序列化 將會導(dǎo)致invalidclassexception。可序列化類可以通過聲明名為"serialversionuid"的字段(該字段必須是靜態(tài) (static)、最終(final)的long型字段)顯式聲明其自己的serialversionuid

如果可序列化類未顯式聲明serialversionuid,則序列化運行時將基于該類的各個方面計算該類的默認serialversionuid值,如“

java(tm)對象序列化規(guī)范”中所述。不過,強烈建議所有可序列化類都顯式聲明serialversionuid值,原因計算默認的 serialversionuid對類的詳細信息具有較高的敏感性,根據(jù)編譯器實現(xiàn)的不同可能千差萬別,這樣在反序列化過程中可能會導(dǎo)致意外的 invalidclassexception。因此,為保證serialversionuid值跨不同java編譯器實現(xiàn)的一致性,序列化類必須聲明一個 明確的serialversionuid值。還強烈建議使用private修改器顯示聲明serialversionuid(如果可能),原因是這種聲明 僅應(yīng)用于立即聲明類--serialversionuid字段作為繼承成員沒有用處。

java.io.serializable引發(fā)的問題——什么是序列化?在什么情況下將類序列化?

序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內(nèi)容進行流化。可以對流化后的對象進行讀寫操作,也可將流化后的對象傳輸于網(wǎng)絡(luò)之間。 序列化是為了解決在對對象流進行讀寫操作時所引發(fā)的問題。序列化的實現(xiàn):將需要被序列化的類實現(xiàn)serializable接口,該接口沒有需要實現(xiàn)的方 法,implementsserializable只是為了標注該對象是可被序列化的,然后使用一個輸出流(如:fileoutputstream)來構(gòu) 造一個objectoutputstream(對象流)對象,接著,使用objectoutputstream對象的 writeobject(objectobj)方法就可以將參數(shù)為obj的對象寫出(即保存其狀態(tài)),要恢復(fù)的話則用輸入流。

序列化:序列化是將對象轉(zhuǎn)換為容易傳輸?shù)母袷降倪^程。例如,可以序列化一個對象,然后使用http通過internet在客戶端和服務(wù)器之間傳輸該對象。在另一端,反序列化將從該流重新構(gòu)造對象。是對象永久化的一種機制。確切的說應(yīng)該是對象的序列化,一般程序在運行時,產(chǎn)生對象,這些對象隨著程序的停止運行而消失,但如果我們想把某些對象(因為是對象,所以有各自不同 的特性)保存下來,在程序終止運行后,這些對象仍然存在,可以在程序再次運行時讀取這些對象的值,或者在其他程序中利用這些保存下來的對象。這種情況下就 要用到對象的序列化。

只有序列化的對象才可以

服務(wù)器硬盤上把序列化的對象取出,然后通過網(wǎng)絡(luò)傳到客戶端,再由客戶端把序列化的對象讀入內(nèi)存,執(zhí)行相應(yīng)的處理。

對象序列化是java的一個特征,通過該特征可以將對象寫作一組字節(jié)碼,當(dāng)在其他位置讀到這些字節(jié)碼時,可以依此創(chuàng)建一個新的對象,而且新對象的狀態(tài) 與原對象完全相同。為了實現(xiàn)對象序列化,要求必須能夠訪問類的私有變量,從而保證對象狀態(tài)能夠正確的得以保存和恢復(fù)。相應(yīng)的,對象序列化api能夠在對象 重建時,將這些值還原給私有的數(shù)據(jù)成員。這是對java語言訪問權(quán)限的挑戰(zhàn)。通常用在服務(wù)器客戶端的對象

交換上面,另外就是在本機的存儲。

對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整性和可傳遞性。譬如通過網(wǎng)絡(luò)傳輸,或者把一個對象保存成一個文件的時候,要實現(xiàn)序列化接口。

即使你沒有用過對象序列化(serialization),你可能也知道它。但你是否知道

java還支持另外一種形式的對象持久化,外部化(externalization)?

下面是序列化和外部化在代碼級的關(guān)聯(lián)方式:

序列化和外部化的主要區(qū)別

外部化和序列化是實現(xiàn)同一目標的兩種不同方法。下面讓我們分析一下序列化和外部化之間的主要區(qū)別。

通過serializable接口對對象序列化的支持是內(nèi)建于核心api的,但是java.io.externalizable的所有實現(xiàn)者必須提供 讀取和寫出的實現(xiàn)。java已經(jīng)具有了對序列化的內(nèi)建支持,也就是說只要制作自己的類java.io.serializable,java就會試圖存儲和 重組你的對象。如果使用外部化,你就可以選擇完全由自己完成讀取和寫出的工作,java對外部化所提供的唯一支持是接口:

序列化會自動存儲必要的信息,用以反序列化被存儲的實例,而外部化則只保存被存儲的類的標識。當(dāng)你通過java.io.serializable接口 序列化一個對象時,有關(guān)類的信息,比如它的屬性和這些屬性的類型,都與實例數(shù)據(jù)一起被存儲起來。在選擇走externalizable這條路時,java 只存儲有關(guān)每個被存儲類型的非常少的信息。

每個接口的優(yōu)點和缺點

序列化:

JAVA?優(yōu)點:內(nèi)建支持

?優(yōu)點:易于實現(xiàn)

?缺點:占用空間過大

?缺點:由于額外的開銷導(dǎo)致速度變比較慢

外部化

?優(yōu)點:開銷較少(程序員決定存儲什么)

?優(yōu)點:可能的速度提升

?缺點:虛擬機不提供任何幫助,也就是說所有的工作都落到了開發(fā)人員的肩上。

在兩者之間如何選擇要根據(jù)應(yīng)用程序的需求來定。serializable通常是最簡單的解決方案,但是它可能會導(dǎo)致出現(xiàn)不可接受的性能問題或空間問題;在出現(xiàn)這些問題的情況下,externalizable可能是一條可行之路。

要記住一點,如果一個類是可外部化的(externalizable),那么externalizable方法將被用于序列化類的實例,即使這個類型提供了serializable方法:

序列化機制的用途

通過對象的序列化我們可以得到對象狀態(tài)信息的字節(jié)流數(shù)據(jù),這些數(shù)據(jù)代表了當(dāng)前對象的狀態(tài)。當(dāng)對象轉(zhuǎn)化成二進制數(shù)據(jù)流之后,我們可以通過多種方式處理它,比如可以通過Socket將 數(shù)據(jù)發(fā)送的遠程主機,又或者保存的本地文件中以期后用。同時,當(dāng)我們通過某種方式獲取了對象序列化之后的二進制數(shù)據(jù)之后,通過反序列化機制實現(xiàn)對象的重 建,恢復(fù)之前對象的狀態(tài)。由此可知,序列化后的字節(jié)流可以應(yīng)用到任何想要重建對象的地方,您所需要的就是獲取這些字節(jié)流數(shù)據(jù)。

Java序列化機制解析

實現(xiàn)序列化的方式

Java API提供了對序列化的支持,要實現(xiàn)對象的序列化和反序列化,基本上包括兩個步驟:

1.聲明對象具有可序列化的能力

2.通過Java API實現(xiàn)具體的序列化處理

在Java語言中,聲明對象具有可序列化的能力主要有兩種方式:其一,實現(xiàn)Serializable接口;其二,實現(xiàn)Externalizable接口。兩者既有區(qū)別又有聯(lián)系。Java從JDK1.1開始支持對象的序列化機制,Serializable接口沒有聲明任何方法,實現(xiàn)該接口的Java類不需要對任何方法提供實現(xiàn)(默認情況下,定制序列化時除外),因此,該接口僅僅是一個”mark interface”,實現(xiàn)該接口意味著告知JVM該對象可以序列化。Java序列化機制要求所有具備序列化的對象必須實現(xiàn)該接口,否則是不能被序列化的,如果對于沒有實現(xiàn)該接口的對象進行序列化時,Java API會拋出異常,無法進行序列化。

Serializable接 口提供了默認的序列化行為,在默認情況下,開發(fā)人員只需實現(xiàn)該接口,無需進行其他額外的操作,即可實現(xiàn)的對象的序列化。當(dāng)然,所謂默認的處理,必然隱藏著 對序列化對象的默認操作,比如對象的哪些屬性被序列化。默認情況下,只對對象中非靜態(tài)的字段(對象的成員數(shù)據(jù)也會被保存,不能序列化任何成員方法和靜態(tài)成員變量)以及非瞬時的字段(transient,只能用來修飾字段)進行序列化,其他的字段是不允許被序列化的。 這種情況的具體表現(xiàn)就是,在序列化的有序字節(jié)流中沒有保存不能被序列化的字段的狀態(tài),因此,在反序列化時,這些字段狀態(tài)是不能被重建的。但是有一點需要注 意的是,經(jīng)過反序列化后的對象,除了對可被序列化的字段狀態(tài)進行重建之外,其他的沒有被序列化的字段作為對象屬性的一部分,也在對象重建時得以初始化。但 是這些字段的狀態(tài)是不被保存的,重建后的這些屬性僅僅是系統(tǒng)賦予的默認值,而非保存了對象序列化之前的狀態(tài)

實現(xiàn)Serializable接口除了提供默認的序列化方式之外,同樣允許開發(fā)人員定制序列化,即通過實現(xiàn)以下相同簽名的方法來實現(xiàn):

序列化方法:

private void writeObject(java.io.ObjectOutputStream out) throws IOException

反序列化方法:

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

其中,writeObject方法用于定制序列化,readObject方法用于實現(xiàn)定制反序列化。

在示例代碼中會對這兩個方法的使用進行展示。

Externalizable接口集成自Serializable接口,實現(xiàn)該接口意味著對象本身完全掌控自己的序列化方式。該接口JDK源碼如下:

Externalizable接口定義了兩個方法:writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。write方法用于實現(xiàn)定制序列化,read方法用于實現(xiàn)定制反序列化

基于Externalizable接口的定制和基于Serializable接口的定制有所不同。基于Externalizable接口的定制是通過實現(xiàn)上述兩個方法實現(xiàn)的,而且方法中操作的參數(shù)也不一樣,基于Externalizable接口是通過操作ObejectOutput類和ObjectInput類實現(xiàn)的。而基于Serializable接口是通過操作ObjectOutputStream類和ObjectInputStream類實現(xiàn)的。至于兩種方式的序列化定制方式會在稍后的示例中進行展示。

哪些數(shù)據(jù)被序列化了?

我們采用默認的序列化方式(僅僅直接實現(xiàn)Serializable接口)序列化對象時,默認的,只將非靜態(tài)的和no-transient字段進行序列化,除此之外的其他域在默認情況下是不進行序列化的,也就是說,在序列化的字節(jié)流數(shù)據(jù)中沒有對這兩種類型數(shù)據(jù)的記錄。這是為什么呢?如果是從代碼級別上分析,從JDK源碼可知,JDK源碼進行了默認的處理,然后將靜態(tài)屬性和瞬時屬性排除在序列化之外,但為什么會選擇這樣的實現(xiàn)呢?我們都知道,對象序列化的本質(zhì)是將對象的狀態(tài)通過有序字節(jié)流進行保存或傳輸,由此,問題的焦點應(yīng)該是對象的狀態(tài)。靜態(tài)變量是類變量,屬于整個類,并非專屬于每個對象實例,因此,不序列化靜態(tài)變量時合理的。瞬時變量,指的是被transient關(guān)鍵字修飾的變量,該關(guān)鍵字表示為瞬時的,即不做持久化處理的,以此來控制屬性是否被包含進入序列化的字節(jié)流中。因此,在序列化時,排除transient關(guān)鍵字修飾的屬性也是合理的。

需要注意的是,沒有被序列化的屬性不會出現(xiàn)在序列化后的有序字節(jié)流中,但是,我們在反 序列化時,是可以訪問這些變量的。這是因為,序列化的過程保存了你所期望保存的對象的狀態(tài)(屬性當(dāng)前值),反序列化就是重建對象的過程,在這個過程中,字 節(jié)流中所保存的對象狀態(tài)被重新賦予了新建的對象。此時,對于沒有被序列化的屬性也是存在的,因為其是類定義的一部分,在新建的對象中是必然存在的。唯一不 同的是,他們的值是類定義的默認值,而非是來自字節(jié)流中保存的狀態(tài)。這也恰恰反映了序列化的本質(zhì):保存對象的狀態(tài)。

那么,到底哪些數(shù)據(jù)被序列化到了有序字節(jié)流中呢?字節(jié)流數(shù)據(jù)包含了non-static和non-transient類型的成員數(shù)據(jù)、類名、類簽名、可序列化的基類以及類中引用的其他對象。

針對于父類,有幾點原則:

1.如果基類實現(xiàn)了Serializable接口,則其子類默認的是可序列化的,不必顯示聲明;

2.如果基類沒有實現(xiàn)Serializable接口,在反序列化時,會調(diào)用基類的無參構(gòu)造方法,重建基類對象只不過是不會保留基類對象狀態(tài)。

基于Serializable接口

默認序列化

基于Serializable接口的默認序列化步驟為:首先,進行序列化聲明,代碼如下:

實現(xiàn)了Serializable接口的Java類與我們平時定義Java類沒有太大區(qū)別,唯一需要注意的是serialVersionUID屬性。每個實現(xiàn)Serializable接口的對象都有一個serialVersionUID,長整型,64位,唯一標示了該序列化對象。在類定義中,可以顯示的定義該靜態(tài)變量,也可以不定義。在不定義的情況下,Java編譯器會隱式的生成該變量。強烈建議顯示定義。那么,該變量有什么用途呢?反序列化兼容控制serialVersionUID相同才能進行反序列化。例如:遠程主機需要反序列化對象C,如果在本地和遠程主機內(nèi)的C對象持有的serialVersionUID不同,即使兩個類其它部分完全一樣,也是不能成功反序列化話的,會拋出異常。因此,如果對類做了修改,為了保證版本對序列化兼容,該變量的值保持不變。從另一個角度來講,不期望不同版本的類對序列化兼容,則改變該變量值。

然后,通過Java API進行實際的序列化處理。我們選擇的場景是:將對象進行序列化,然后保存到本地文件中。然后,從本地文件讀取序列化后的有序字節(jié)流,進行反序列化,重建對象。代碼示例如下:

說明:在實際的對象實例化過程中,涉及到的Java類是ObjectOutputStream和ObjectInputStream。這兩個類負責(zé)對象序列化的主要工作。

定制序列化

上面的代碼示例中,展示了最為基本的默認的對象序列化和反序列化方式。之所以稱之為是基本的,是因為,我們在對自定義的類進行序列化時完全沒有進行任何“干涉”, 系統(tǒng)默認的選擇了類定義中符合規(guī)則的屬性進行序列化,因此這是一種默認的方式。與之相對應(yīng)的是,我們可以定制序列化及反序列化,以滿足實際的需要。例如: 序列化的對象一般在網(wǎng)絡(luò)上進行傳輸,所以安全性是必須要考慮的問題。大部分情況下,我們期望對類似于密碼等這樣的敏感信息進行加密處理,以密文的形式在網(wǎng) 絡(luò)間傳輸,增強數(shù)據(jù)的安全性。但是,通過我們上述的方式進行序列化,默認的處理方式是不能保證密碼的密文傳輸?shù)摹R虼耍槍Υ祟悊栴},我們必須能夠定制對 象的序列化和反序列化過程,只有這樣才能將我們的業(yè)務(wù)邏輯加入其中,以滿足實際應(yīng)用的需要。

如何實現(xiàn)定制呢?

定制序列化和反序列化與上述序列化方式的不同在于:自定義類的實現(xiàn)。

首先,同樣,自定義的類要實現(xiàn)Serializable接口,這是序列化處理的前提。不同的是,在定制序列化時,需要根據(jù)我們的實際需要,重寫writeObject和readObject方法,完成序列化和反序列化的定制。示例代碼如下:

說明:定制序列化過程中,序列化和反序列化讀取信息的順序要保持一致,否則會出現(xiàn)意想不到的后果。

基于Externalizable接口

實現(xiàn)Extenalizable接口的類將完全由自己控制自身的序列化和反序列化。示例代碼如下:

對象序列化及反序列化測試代碼:

測試輸出結(jié)果為:

序列化帶來的問題

網(wǎng)絡(luò)傳輸?shù)陌踩?/a>

對象進行序列化之后轉(zhuǎn)化成有序的字節(jié)流在網(wǎng)絡(luò)上進行傳輸,如果通過默認的序列化方式, 則代碼都是以明文的方式進行傳輸。這種情況下,部分字段的安全性是不能保障的,特別是像密碼這樣的安全敏感的信息。因此,如果您需要對部分字段信息進行特 殊的處理,那么應(yīng)當(dāng)選擇定制對象的序列化方式,例如對密碼等敏感信息進行加密處理。

類自身封裝的安全性

對對象進行序列化時,類中所定義的被private、final等 訪問控制符所修飾的字段是直接忽略這些訪問控制符而直接進行序列化的,因此,原本在本地定義的想要一次控制字段的訪問權(quán)限的工作都是不起作用的。對于序列 化后的有序字節(jié)流來說一切都是可見的,而且是可重建的。這在一定程度上削弱了字段的安全性。因此,如果您需要特別處理這些信息,您可以選擇相應(yīng)的方式對這 些屬性進行加密或者其他可行的處理,以盡量保持數(shù)據(jù)的安全性。

總結(jié)

1.通過序列化和反序列化實現(xiàn)了對象狀態(tài)的保存、傳輸以及對象的重建。在進行對象序列化時,開發(fā)人員可以根據(jù)自身情況,靈活選擇默認方式或者自定義方式實現(xiàn)對象的序列化和反序列化。

2.序列化機制是Java中對輕量級持久化的支持

3.序列化的字節(jié)流數(shù)據(jù)在網(wǎng)上傳輸?shù)陌踩珕栴}需要引起大家足夠的注意。

4.序列化破壞了原有類的數(shù)據(jù)的”安全性“,例如private屬性不起作用的。


序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內(nèi)容進行流化。可以對流化后的對象進行讀寫操作,也可將流化后的對象傳輸于網(wǎng)絡(luò)之間。序列化是為了解決對象流讀寫操作時可能引發(fā)的問題(如果不進行序列化可能會存在數(shù)據(jù)亂序的問題)。

要實現(xiàn)序列化,需要讓一個類實現(xiàn)Serializable接口,該接口是一個標識性接口,標注該類對象是可被序列化的,然后使用一個輸出流來構(gòu)造一個對象輸出流并通過writeObject(Object)方法就可以將實現(xiàn)對象寫出(即保存其狀態(tài));如果需要反序列化則可以用一個輸入流建立對象輸入流,然后通過readObject方法從流中讀取對象。序列化除了能夠?qū)崿F(xiàn)對象的持久化之外,還能夠用于對象的深度克隆

1、java序列化簡介

序列化就是指對象通過寫出描述自己狀態(tài)的數(shù)值來記錄自己的過程,即將對象表示成一系列有序字節(jié),java提供了將對象寫入流和從流中恢復(fù)對象的方法。對象能包含其它的對象,而其它的對象又可以包含另外的對象JAVA序列化能夠自動的處理嵌套的對象。對于一個對象的簡單域,writeObject()直接將其值寫入流中。當(dāng)遇到一個對象域時,writeObject()被再次調(diào)用,如果這個對象內(nèi)嵌另一個對象,那么,writeObject()又被調(diào)用,直到對象能被直接寫入流為止。程序員所需要做的是將對象傳入ObjectOutputStream的writeObject()方法,剩下的將有系統(tǒng)自動完成。

實現(xiàn)序列化的類必須實現(xiàn)java.io.Serializable或java.io.Externalizable接口,否則將產(chǎn)生一個NotSerializableException。該接口內(nèi)部并沒有任何方法,它只是一個"tagging interface",僅僅"tags"它自己的對象是一個特殊的類型。類通過實現(xiàn)java.io.Serializable接口以啟用其序列化功能。未實現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化。序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于標識可序列化的語義。Java的"對象序列化"能讓你將一個實現(xiàn)了Serializable接口的對象轉(zhuǎn)換成一組byte,這樣日后要用這個對象時候,你就能把這些byte數(shù)據(jù)恢復(fù)出來,并據(jù)此重新構(gòu)建那個對象了。

序列化圖示

序列化圖示

序列化的時候,writeObject與readObject之間是有先后順序的。readObject將最先write的object read出來。用數(shù)據(jù)結(jié)構(gòu)的術(shù)語來講就稱之為先進先出

2、序列化的必要性及目的

Java中,一切都是對象,在分布式環(huán)境中經(jīng)常需要將Object從這一端網(wǎng)絡(luò)或設(shè)備傳遞到另一端。這就需要有一種可以在兩端傳輸數(shù)據(jù)的協(xié)議。Java序列化機制就是為了解決這個問題而產(chǎn)生。

Java序列化支持的兩種主要特性:

Java的RMI使本來存在于其他機器的對象可以表現(xiàn)出就像本地機器上的行為。

將消息發(fā)給遠程對象時,需要通過對象序列化傳輸參數(shù)和返回值.

Java序列化的目的:

支持運行在不同虛擬機上不同版本類之間的雙向通訊

定義允許JAVA讀取用相同類較老版本寫入的數(shù)據(jù)流機制

定義允許JAVA寫用相同類較老版本讀取的數(shù)據(jù)流機制

提供對持久性和RMI序列化

產(chǎn)生壓縮流且運行良好以使RMI序列化

辨別寫入的是否是本地流

保持非版本化類的低負載

3、序列化異常

序列化對象期間可能拋出6種異常:

InvalidClassException通常在重序列化流無法確定類型時返回的類無法在取得對象的系統(tǒng)中表示時拋出此異常異常也在恢復(fù)的類不聲明為public時或沒有public缺省(無變元)構(gòu)造器時拋出。

NotSerializableException通常由具體化對象(負責(zé)自身的重序列化)探測到輸入流錯誤時拋出。錯誤通常由意外不變量值指示,或者表示要序列化的對象不可序列化

StreamCorruptedException在存放對象的頭或控制數(shù)據(jù)無效時拋出。

OptionalDataException流中應(yīng)包含對象但實際只包含原型數(shù)據(jù)時拋出

ClassNotFoundException流的讀取端找不到反序列化對象的類時拋出

IOException要讀取或?qū)懭?/b>的對象發(fā)生與流有關(guān)的錯誤時拋出。

4、序列化一個對象

序列化一個對象,以及對序列化后的對象進行操作,需要遵循以下3點:

1、 一個對象能夠序列化的前提是實現(xiàn)Serializable接口或Externalizable接口,Serializable接口沒有方法,更像是個標記。有了這個標記的Class就能被序列化機制處理。

2、?寫個程序將對象序列化并輸出。ObjectOutputStream能把Object輸出成Byte流。

3、 要從持久的文件中讀取Bytes重建對象,我們可以使用ObjectInputStream。

在序列化時,有幾點要注意的:

當(dāng)一個對象被序列化時,只序列化對象的非靜態(tài)成員變量Non-static,不能序列化任何成員方法和靜態(tài)成員變量,不能序列化Non-transient變量。

如果一個對象的成員變量是一個對象,那么這個對象的數(shù)據(jù)成員也會被保存。

如果一個可序列化的對象包含對某個不可序列化的對象的引用,那么整個序列化操作將會失敗,并且會拋出一個NotSerializableException。可以通過將這個引用標記為transient,那么對象仍然可以序列化。對于一些比較敏感的不想序列化的數(shù)據(jù),也可以采用該標識進行修飾。

http://www.cnblogs.com/redcreen/articles/1955307.html

如何序列化一個對象

一個對象能夠序列化的前提是實現(xiàn)Serializable接口,Serializable接口沒有方法,更像是個標記。

有了這個標記的Class就能被序列化機制處理。

import java.io.Serializable;

class TestSerial implements Serializable {

public byte version= 100;

public byte count= 0;

}

然后我們寫個程序?qū)ο笮蛄谢⑤敵觥bjectOutputStream能把Object輸出成Byte流。

我們將Byte流暫時存儲到temp.out文件里。

public static void main(String args[]) throws IOException {

FileOutputStream fos = new FileOutputStream("temp.out");

ObjectOutputStream oos = new ObjectOutputStream(fos);

TestSerial ts = new TestSerial();

oos.writeObject(ts);

oos.flush();

oos.close();

}

如果要從持久的文件中讀取Bytes重建對象,我們可以使用ObjectInputStream。

public static void main(String args[]) throws IOException {

FileInputStream fis = new FileInputStream("temp.out");

ObjectInputStream oin = new ObjectInputStream(fis);

TestSerial ts = (TestSerial) oin.readObject();

System.out.println("version="+ts.version);

}

執(zhí)行結(jié)果為100.

對象的序列化格式

將一個對象序列化后是什么樣子呢?打開剛才我們將對象序列化輸出的temp.out文件

以16進制方式顯示。內(nèi)容應(yīng)該如下:

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65

73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05

63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78

70 00 64

這一坨字節(jié)就是用來描述序列化以后的TestSerial對象的,我們注意到TestSerial類中只有兩個域:

publicbyte version = 100;

publicbyte count = 0;

且都是byte型,理論上存儲這兩個域只需要2個byte,但是實際上temp.out占據(jù)空間為51bytes,也就是說除了數(shù)據(jù)以外,還包括了對序列化對象的其他描述

Java的序列化算法

序列化算法一般會按步驟做如下事情:

將對象實例相關(guān)的類元數(shù)據(jù)輸出。【元數(shù)據(jù)】

◆遞歸地輸出類的超類描述直到不再有超類。【超類描述】

◆類元數(shù)據(jù)完了以后,開始從最頂層的超類開始輸出對象實例的實際數(shù)據(jù)值。【超類-類的實際數(shù)據(jù)值】

◆從上至下遞歸輸出實例的數(shù)據(jù)【實例數(shù)據(jù)值】

我們用另一個更完整覆蓋所有可能出現(xiàn)的情況的例子來說明:

class parent implements Serializable {

int parentVersion= 10;

}

class contain implements Serializable{

Int containVersion= 11;

}

public class SerialTest extends parent implements Serializable {

int version= 66;

contain con = new contain();

public int getVersion(){

return version;

}

public static void main(String args[]) throws IOException {

FileOutputStream fos = new FileOutputStream("temp.out");

ObjectOutputStream oos = new ObjectOutputStream(fos);

SerialTest st = new SerialTest();

oos.writeObject(st);

oos.flush();

oos.close();

}

}

AC ED: STREAM_MAGIC.聲明使用了序列化協(xié)議.

00 05: STREAM_VERSION.序列化協(xié)議版本.

0x73: TC_OBJECT.聲明這是一個新的對象.

0x72: TC_CLASSDESC.聲明這里開始一個新Class。

00 0A: Class名字的長度.

53 65 72 69 61 6c 54 65 73 74:SerialTest,Class類名.

05 52 81 5A AC 66 02 F6:SerialVersionUID,序列化ID,如果沒有指定,

則會由算法隨機生成一個8byte的ID.

0x02:標記號.該值聲明該對象支持序列化。

00 02:該類所包含的域個數(shù)。

0x49:域類型. 49代表"I",也就是Int.

00 07:域名字的長度.

76 65 72 73 69 6F 6E: version,域名字描述.

0x4C:域的類型.

00 03:域名字長度.

63 6F 6E:域名字描述,con

0x74: TC_STRING.代表一個new String.用String來引用對象。

00 09:該String長度.

4C 63 6F 6E 74 61 69 6E 3B:Lcontain;, JVM的標準對象簽名表示法.

0x78: TC_ENDBLOCKDATA,對象數(shù)據(jù)塊結(jié)束的標志

0x72: TC_CLASSDESC.聲明這個是個新類.

00 06:類名長度.

70 61 72 65 6E 74: parent,類名描述。

0E DB D2 BD 85 EE 63 7A:SerialVersionUID,序列化ID.

0x02:標記號.該值聲明該對象支持序列化.

00 01:類中域的個數(shù).

0x49:域類型. 49代表"I",也就是Int.

00 0D:域名字長度.

70 61 72 65 6E 74 56 65 72 73 69 6F 6E:parentVersion,域名字描述。

0x78: TC_ENDBLOCKDATA,對象塊結(jié)束的標志。

0x70: TC_NULL,說明沒有其他超類的標志。.

0000000A: 10,parentVersion域的值.

00000042: 66, version域的值.

0x73: TC_OBJECT,聲明這是一個新的對象.

0x72: TC_CLASSDESC聲明這里開始一個新Class.

00 07:類名的長度.

63 6F 6E 74 61 69 6E: contain,類名描述.

FC BB E6 0E FB CB 60 C7:SerialVersionUID,序列化ID.

0x02: Various flags.標記號.該值聲明該對象支持序列化

00 01:類內(nèi)的域個數(shù)。

0x49:域類型. 49代表"I",也就是Int..

00 0E:域名字長度.

63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E:containVersion,域名字描述.

0x78: TC_ENDBLOCKDATA對象塊結(jié)束的標志.

0x70:TC_NULL,沒有超類了。

0000000B: 11,containVersion的值.

這個例子是相當(dāng)?shù)闹卑桌病erialTest類實現(xiàn)了Parent超類,內(nèi)部還持有一個Container對象。

序列化后的格式如下:

AC ED 00 05 7372 00 0A 53 65 72 69 61 6C 54 65

73 74 05 52 81 5A AC 66 02 F6 02 00 0249 00 07

76 65 72 73 69 6F 6E4C00 03 63 6F 6E74 00 09

4C63 6F 6E 74 61 69 6E 3B 7872 00 06 70 61 72

65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 0149 00

0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70

0000000A 0000004273 72 00 07 63 6F 6E 74

61 69 6E FC BB E6 0E FB CB 60 C7 02 00 0149 00

0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78

700000000B

我們來仔細看看這些字節(jié)都代表了啥。

開頭部分,見顏色:

AC ED: STREAM_MAGIC.聲明使用了序列化協(xié)議.

00 05: STREAM_VERSION.序列化協(xié)議版本.

0x73: TC_OBJECT.聲明這是一個新的對象.

序列化算法的第一步就是輸出對象相關(guān)類的描述。例子所示對象為SerialTest類實例,因此接下來輸出SerialTest類的描述。見顏色:

0x72: TC_CLASSDESC.聲明這里開始一個新Class。

00 0A: Class名字的長度.

53 65 72 69 61 6c 54 65 73 74:SerialTest,Class類名.

05 52 81 5A AC 66 02 F6:SerialVersionUID,序列化ID,如果沒有指定,則會由算法隨機生成一個8byte的ID.

0x02:標記號.該值聲明該對象支持序列化

00 02:該類所包含的域個數(shù)。

接下來,算法輸出其中的一個域,intversion=66;見顏色:

0x49:域類型. 49代表"I",也就是Int.

00 07:域名字的長度.

76 65 72 73 69 6F 6E: version,域名字描述.

然后,算法輸出下一個域,contain con = new contain();這個有點特殊,是個對象。描述對象類型引用時需要使用JVM的標準對象簽名表示法,見顏色:

0x4C:域的類型.

00 03:域名字長度.

63 6F 6E:域名字描述,con

0x74: TC_STRING.代表一個new String.用String來引用對象。

00 09:該String長度.

4C 63 6F 6E 74 61 69 6E 3B:Lcontain;, JVM的標準對象簽名表示法.

0x78: TC_ENDBLOCKDATA,對象數(shù)據(jù)塊結(jié)束的標志

.接下來算法就會輸出超類也就是Parent類描述了,見顏色:

0x72: TC_CLASSDESC.聲明這個是個新類.

00 06:類名長度.

70 61 72 65 6E 74: parent,類名描述。

0E DB D2 BD 85 EE 63 7A:SerialVersionUID,序列化ID.

0x02:標記號.該值聲明該對象支持序列化.

00 01:類中域的個數(shù).

下一步,輸出parent類的域描述,intparentVersion=100;同見顏色:

0x49:域類型. 49代表"I",也就是Int.

00 0D:域名字長度.

70 61 72 65 6E 74 56 65 72 73 69 6F 6E:parentVersion,域名字描述。

0x78: TC_ENDBLOCKDATA,對象塊結(jié)束的標志。

0x70: TC_NULL,說明沒有其他超類的標志。.

到此為止,算法已經(jīng)對所有的類的描述都做了輸出。下一步就是把實例對象的實際值輸出了。這時候是從parent Class的域開始的,見顏色:

0000000A: 10,parentVersion域的值.

還有SerialTest類的域:

00000042: 66, version域的值.

再往后的bytes比較有意思,算法需要描述contain類的信息,要記住,現(xiàn)在還沒有對contain類進行過描述,見顏色:

0x73: TC_OBJECT,聲明這是一個新的對象.

0x72: TC_CLASSDESC聲明這里開始一個新Class.

00 07:類名的長度.

63 6F 6E 74 61 69 6E: contain,類名描述.

FC BB E6 0E FB CB 60 C7:SerialVersionUID,序列化ID.

0x02: Various flags.標記號.該值聲明該對象支持序列化

00 01:類內(nèi)的域個數(shù)。

.輸出contain的唯一的域描述,intcontainVersion=11;

0x49:域類型. 49代表"I",也就是Int..

00 0E:域名字長度.

63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E:containVersion,域名字描述.

0x78: TC_ENDBLOCKDATA對象塊結(jié)束的標志.

這時,序列化算法會檢查contain是否有超類,如果有的話會接著輸出。

0x70:TC_NULL,沒有超類了。

最后,將contain類實際域值輸出。

0000000B: 11,containVersion的值.

serialVersionUID值的重要作用

根據(jù)上面的分析,可以發(fā)現(xiàn)如果一個類可序列化,serialVersionUID建議給一個確定的值,不要由系統(tǒng)自動生成,否則在增減字段(不能修改字段類型及長度)時,如果兩邊的類的版本不同會導(dǎo)致反序列化失敗.

注意問題

如果序列化時代碼這樣寫:

SerialTest st = new SerialTest();

oos.writeObject((parent)st);

會發(fā)現(xiàn)序列化的對象依然是SerialTest,如果在分布式環(huán)境中用Parent反序列化(調(diào)用段不存在SerialTest),會造成ClassNotFoundException.

7.1 定制數(shù)據(jù)格式的序列化

驗證怎樣用writeObject和readObject方法編碼一個定制數(shù)據(jù)格式。當(dāng)有大量持久性數(shù)據(jù)時,數(shù)據(jù)應(yīng)該以簡潔、精簡的格式存放。此例子用一個矩形對稱陣列,只對其一半數(shù)據(jù)序列化,即只寫/讀一半數(shù)據(jù)再恢復(fù)成完整陣列。

7.2 非序列化超類的序列化

當(dāng)一個已序列化的子類的超類沒有序列化時,子類必須顯式存儲超類的狀態(tài)。

7.3 超類具體化的具體化

當(dāng)用具體化接口時,一個具體化對象必須運行writeExternal()方法存儲對象的狀態(tài),用readExternal方法讀取對象的狀態(tài)。此例子驗證了一個怎樣存儲和恢復(fù)它可具體化超類對象的狀態(tài)。當(dāng)一個可具體化對象的超類也具體化,子類要在它自己的writeExternal()和readExternal()方法中調(diào)用其超類的writeExternal()和readExternal()方法。

7.4 超類非具體化的具體化

當(dāng)用具體化接口時,一個具體化對象必須運行writeExternal()方法存儲對象的狀態(tài),用readExternal()方法讀取對象的狀態(tài)。此例子驗證了一個對象怎樣存儲和恢復(fù)它非具體化超類的狀態(tài)。當(dāng)一個可具體化對象的超類沒有具體化,子類必須用它自己的writeExternal()和readExternal()方法明確存儲和恢復(fù)其超類的可具體化對象狀態(tài)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 1. Java序列化和反序列化(What) Java序列化(Serialize)是指將一個Java對象寫入IO流中...
    悠揚前奏閱讀 891評論 2 1
  • 序列化簡介 Java序列化,一個日常開發(fā)中比較少用到的技術(shù)。正常情況下,JVM啟動后,我們可以創(chuàng)建對象,JVM關(guān)閉...
    不知名的蛋撻閱讀 549評論 1 0
  • 什么是序列化? 序列化是將對象存儲為二進制格式。在序列化的過程中,對象和它的元數(shù)據(jù)(比如對象的類名和它的屬性名稱)...
    Chokez閱讀 1,108評論 0 0
  • 1 序列化的原因 java序列化主要是為了跨平臺,實現(xiàn)對象的一致性,可在不同的平臺上,保持自己原有的屬性和方法不變...
    唐一川閱讀 583評論 0 2
  • 畫一樂乎一家閱讀 416評論 1 2