序列化和反序列化

JAVA序列化機(jī)制的深入研究

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

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

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

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

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

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

序列化及反序列化介紹

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

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

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

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

c)當(dāng)你想通過(guò)RMI傳輸對(duì)象的時(shí)候;

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

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

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

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

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

c)當(dāng)一個(gè)對(duì)象的實(shí)例變量引用其他對(duì)象,序列化該對(duì)象時(shí)也把引用對(duì)象進(jìn)行序列化;

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

1.安全方面的原因,比如一個(gè)對(duì)象擁有private,public等f(wàn)ield,對(duì)于一個(gè)要傳輸?shù)膶?duì)象,比如寫(xiě)到文件,或者進(jìn)行rmi傳輸?shù)鹊龋谛蛄谢M(jìn)行傳輸?shù)倪^(guò)程中,這個(gè)對(duì)象的private等域是不受保護(hù)的。

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

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

什么時(shí)候使用序列化:

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

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

類通過(guò)實(shí)現(xiàn)java.io.serializable接口以啟用其序列化功能。未實(shí)現(xiàn)此接口的類將無(wú)法使其任何狀態(tài)序列化或反序列化??尚蛄谢惖乃凶宇愋捅旧矶际强尚蛄谢?。序列化接口沒(méi)有方法或字段,僅用于標(biāo)識(shí)可序列化的語(yǔ)義。

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

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

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

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

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

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

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

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

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

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

如果可序列化類未顯式聲明serialversionuid,則序列化運(yùn)行時(shí)將基于該類的各個(gè)方面計(jì)算該類的默認(rèn)serialversionuid值,如“

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

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

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

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

只有序列化的對(duì)象才可以

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

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

交換上面,另外就是在本機(jī)的存儲(chǔ)。

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

即使你沒(méi)有用過(guò)對(duì)象序列化(serialization),你可能也知道它。但你是否知道

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

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

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

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

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

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

每個(gè)接口的優(yōu)點(diǎn)和缺點(diǎn)

序列化:

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

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

?缺點(diǎn):占用空間過(guò)大

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

外部化

?優(yōu)點(diǎn):開(kāi)銷較少(程序員決定存儲(chǔ)什么)

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

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

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

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

序列化機(jī)制的用途

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

Java序列化機(jī)制解析

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

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

1.聲明對(duì)象具有可序列化的能力

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

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

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

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

序列化方法:

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

反序列化方法:

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

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

在示例代碼中會(huì)對(duì)這兩個(gè)方法的使用進(jìn)行展示。

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

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

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

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

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

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

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

針對(duì)于父類,有幾點(diǎn)原則:

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

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

基于Serializable接口

默認(rèn)序列化

基于Serializable接口的默認(rèn)序列化步驟為:首先,進(jìn)行序列化聲明,代碼如下:

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

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

說(shuō)明:在實(shí)際的對(duì)象實(shí)例化過(guò)程中,涉及到的Java類是ObjectOutputStream和ObjectInputStream。這兩個(gè)類負(fù)責(zé)對(duì)象序列化的主要工作。

定制序列化

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

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

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

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

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

基于Externalizable接口

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

對(duì)象序列化及反序列化測(cè)試代碼:

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

序列化帶來(lái)的問(wèn)題

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

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

類自身封裝的安全性

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

總結(jié)

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

2.序列化機(jī)制是Java中對(duì)輕量級(jí)持久化的支持。

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

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


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

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

1、java序列化簡(jiǎn)介

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

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

序列化圖示

序列化圖示

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

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

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

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

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

將消息發(fā)給遠(yuǎn)程對(duì)象時(shí),需要通過(guò)對(duì)象序列化來(lái)傳輸參數(shù)和返回值.

Java序列化的目的:

支持運(yùn)行在不同虛擬機(jī)上不同版本類之間的雙向通訊

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

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

提供對(duì)持久性和RMI序列化;

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

辨別寫(xiě)入的是否是本地流

保持非版本化類的低負(fù)載;

3、序列化異常

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

InvalidClassException通常在重序列化流無(wú)法確定類型時(shí)返回的類無(wú)法在取得對(duì)象的系統(tǒng)中表示時(shí)拋出此異常。異常也在恢復(fù)的類不聲明為public時(shí)或沒(méi)有public缺?。o(wú)變?cè)?gòu)造器時(shí)拋出。

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

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

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

ClassNotFoundException流的讀取端找不到反序列化對(duì)象的類時(shí)拋出

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

4、序列化一個(gè)對(duì)象

序列化一個(gè)對(duì)象,以及對(duì)序列化后的對(duì)象進(jìn)行操作,需要遵循以下3點(diǎn):

1、 一個(gè)對(duì)象能夠序列化的前提是實(shí)現(xiàn)Serializable接口或Externalizable接口,Serializable接口沒(méi)有方法,更像是個(gè)標(biāo)記。有了這個(gè)標(biāo)記的Class就能被序列化機(jī)制處理。

2、?寫(xiě)個(gè)程序將對(duì)象序列化并輸出。ObjectOutputStream能把Object輸出成Byte流。

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

在序列化時(shí),有幾點(diǎn)要注意的:

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

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

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

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

如何序列化一個(gè)對(duì)象

一個(gè)對(duì)象能夠序列化的前提是實(shí)現(xiàn)Serializable接口,Serializable接口沒(méi)有方法,更像是個(gè)標(biāo)記。

有了這個(gè)標(biāo)記的Class就能被序列化機(jī)制處理。

import java.io.Serializable;

class TestSerial implements Serializable {

public byte version= 100;

public byte count= 0;

}

然后我們寫(xiě)個(gè)程序?qū)?duì)象序列化并輸出。ObjectOutputStream能把Object輸出成Byte流。

我們將Byte流暫時(shí)存儲(chǔ)到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重建對(duì)象,我們可以使用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.

對(duì)象的序列化格式

將一個(gè)對(duì)象序列化后是什么樣子呢?打開(kāi)剛才我們將對(duì)象序列化輸出的temp.out文件

以16進(jìn)制方式顯示。內(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é)就是用來(lái)描述序列化以后的TestSerial對(duì)象的,我們注意到TestSerial類中只有兩個(gè)域:

publicbyte version = 100;

publicbyte count = 0;

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

Java的序列化算法

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

將對(duì)象實(shí)例相關(guān)的類元數(shù)據(jù)輸出?!驹獢?shù)據(jù)】

◆遞歸地輸出類的超類描述直到不再有超類?!境惷枋觥?/b>

◆類元數(shù)據(jù)完了以后,開(kāi)始從最頂層的超類開(kāi)始輸出對(duì)象實(shí)例的實(shí)際數(shù)據(jù)值?!境?類的實(shí)際數(shù)據(jù)值】

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

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

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.聲明這是一個(gè)新的對(duì)象.

0x72: TC_CLASSDESC.聲明這里開(kāi)始一個(gè)新Class。

00 0A: Class名字的長(zhǎng)度.

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

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

則會(huì)由算法隨機(jī)生成一個(gè)8byte的ID.

0x02:標(biāo)記號(hào).該值聲明該對(duì)象支持序列化。

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

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

00 07:域名字的長(zhǎng)度.

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

0x4C:域的類型.

00 03:域名字長(zhǎng)度.

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

0x74: TC_STRING.代表一個(gè)new String.用String來(lái)引用對(duì)象。

00 09:該String長(zhǎng)度.

4C 63 6F 6E 74 61 69 6E 3B:Lcontain;, JVM的標(biāo)準(zhǔn)對(duì)象簽名表示法.

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

0x72: TC_CLASSDESC.聲明這個(gè)是個(gè)新類.

00 06:類名長(zhǎng)度.

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

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

0x02:標(biāo)記號(hào).該值聲明該對(duì)象支持序列化.

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

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

00 0D:域名字長(zhǎng)度.

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

0x78: TC_ENDBLOCKDATA,對(duì)象塊結(jié)束的標(biāo)志。

0x70: TC_NULL,說(shuō)明沒(méi)有其他超類的標(biāo)志。.

0000000A: 10,parentVersion域的值.

00000042: 66, version域的值.

0x73: TC_OBJECT,聲明這是一個(gè)新的對(duì)象.

0x72: TC_CLASSDESC聲明這里開(kāi)始一個(gè)新Class.

00 07:類名的長(zhǎng)度.

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

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

0x02: Various flags.標(biāo)記號(hào).該值聲明該對(duì)象支持序列化

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

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

00 0E:域名字長(zhǎng)度.

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

0x78: TC_ENDBLOCKDATA對(duì)象塊結(jié)束的標(biāo)志.

0x70:TC_NULL,沒(méi)有超類了。

0000000B: 11,containVersion的值.

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

序列化后的格式如下:

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

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

開(kāi)頭部分,見(jiàn)顏色:

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

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

0x73: TC_OBJECT.聲明這是一個(gè)新的對(duì)象.

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

0x72: TC_CLASSDESC.聲明這里開(kāi)始一個(gè)新Class。

00 0A: Class名字的長(zhǎng)度.

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

05 52 81 5A AC 66 02 F6:SerialVersionUID,序列化ID,如果沒(méi)有指定,則會(huì)由算法隨機(jī)生成一個(gè)8byte的ID.

0x02:標(biāo)記號(hào).該值聲明該對(duì)象支持序列化

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

接下來(lái),算法輸出其中的一個(gè)域,intversion=66;見(jiàn)顏色:

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

00 07:域名字的長(zhǎng)度.

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

然后,算法輸出下一個(gè)域,contain con = new contain();這個(gè)有點(diǎn)特殊,是個(gè)對(duì)象。描述對(duì)象類型引用時(shí)需要使用JVM的標(biāo)準(zhǔn)對(duì)象簽名表示法,見(jiàn)顏色:

0x4C:域的類型.

00 03:域名字長(zhǎng)度.

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

0x74: TC_STRING.代表一個(gè)new String.用String來(lái)引用對(duì)象。

00 09:該String長(zhǎng)度.

4C 63 6F 6E 74 61 69 6E 3B:Lcontain;, JVM的標(biāo)準(zhǔn)對(duì)象簽名表示法.

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

.接下來(lái)算法就會(huì)輸出超類也就是Parent類描述了,見(jiàn)顏色:

0x72: TC_CLASSDESC.聲明這個(gè)是個(gè)新類.

00 06:類名長(zhǎng)度.

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

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

0x02:標(biāo)記號(hào).該值聲明該對(duì)象支持序列化.

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

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

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

00 0D:域名字長(zhǎng)度.

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

0x78: TC_ENDBLOCKDATA,對(duì)象塊結(jié)束的標(biāo)志。

0x70: TC_NULL,說(shuō)明沒(méi)有其他超類的標(biāo)志。.

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

0000000A: 10,parentVersion域的值.

還有SerialTest類的域:

00000042: 66, version域的值.

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

0x73: TC_OBJECT,聲明這是一個(gè)新的對(duì)象.

0x72: TC_CLASSDESC聲明這里開(kāi)始一個(gè)新Class.

00 07:類名的長(zhǎng)度.

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

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

0x02: Various flags.標(biāo)記號(hào).該值聲明該對(duì)象支持序列化

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

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

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

00 0E:域名字長(zhǎng)度.

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

0x78: TC_ENDBLOCKDATA對(duì)象塊結(jié)束的標(biāo)志.

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

0x70:TC_NULL,沒(méi)有超類了。

最后,將contain類實(shí)際域值輸出。

0000000B: 11,containVersion的值.

serialVersionUID值的重要作用

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

注意問(wèn)題

如果序列化時(shí)代碼這樣寫(xiě):

SerialTest st = new SerialTest();

oos.writeObject((parent)st);

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

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

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

7.2 非序列化超類的序列化

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

7.3 超類具體化的具體化

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

7.4 超類非具體化的具體化

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容