Java序列化(Serializable)與反序列化
來源:易賢網(wǎng) 閱讀:755 次 日期:2015-04-09 17:24:07
溫馨提示:易賢網(wǎng)小編為您整理了“Java序列化(Serializable)與反序列化”,方便廣大網(wǎng)友查閱!

序列化是干什么的

簡單說就是為了保存在內(nèi)存中的各種對象的狀態(tài)(也就是實例變量,不是方法),并且可以把保存的對象狀態(tài)再讀出來。雖然你可以用你自己的各種各樣的方法來保 存object states,但是Java給你提供一種應(yīng)該比你自己好的保存對象狀態(tài)的機制,那就是序列化。

什么情況下需要序列化

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

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

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

序列化的幾種方式

在Java中socket傳輸數(shù)據(jù)時,數(shù)據(jù)類型往往比較難選擇??赡芤紤]帶寬、跨語言、版本的兼容等問題。比較常見的做法有兩種:一是把對象包裝成JSON字符串傳輸,二是采用java對象的序列化和反序列化。隨著Google工具protoBuf的開源,protobuf也是個不錯的選擇。對JSON,Object Serialize,ProtoBuf 做個對比。

Object Serialize

Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進(jìn)行反序列化時,JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)實體(類)的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會出現(xiàn)序列化版本不一致的異常。

serialVersionUID 用來表明類的不同版本間的兼容性。有兩種生成方式:

一個是默認(rèn)的1L,比如:private static final long serialVersionUID = 1L;

一個是根據(jù)類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,比如: private static final long serialVersionUID = xxxxL;

下面來討論Java類中為什么需要重載 serialVersionUID 屬性?

當(dāng)兩個進(jìn)程在進(jìn)行遠(yuǎn)程通信時,彼此可以發(fā)送各種類型的數(shù)據(jù)。無論是何種類型的數(shù)據(jù),都會以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。發(fā)送方需要把這個Java對象轉(zhuǎn)換為字節(jié)序列,才能在網(wǎng)絡(luò)上傳送;接收方則需要把字節(jié)序列再恢復(fù)為Java對象。

把Java對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化。

把字節(jié)序列恢復(fù)為Java對象的過程稱為對象的反序列化。

對象的序列化主要有兩種用途:(1)把對象的字節(jié)序列永久地保存到硬盤上,通常存放在一個文件中; (2)在網(wǎng)絡(luò)上傳送對象的字節(jié)序列;

java.io.ObjectOutputStream代表對象輸出流,它的writeObject(Object obj)方法可對參數(shù)指定的obj對象進(jìn)行序列化,把得到的字節(jié)序列寫到一個目標(biāo)輸出流中。

java.io.ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節(jié)序列,再把它們反序列化為一個對象,并將其返回。

只有實現(xiàn)了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自Serializable接口,實現(xiàn)Externalizable接口的類完全由自身來控制序列化的行為,而僅實現(xiàn)Serializable接口的類可以采用默認(rèn)的序列化方式 。

凡是實現(xiàn)Serializable接口的類都有一個表示序列化版本標(biāo)識符的靜態(tài)變量:private static final long serialVersionUID;

類的serialVersionUID的默認(rèn)值完全依賴于Java編譯器的實現(xiàn),對于同一個類,用不同的Java編譯器編譯,有可能會導(dǎo)致不同的serialVersionUID,也有可能相同。為了提高serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,為它賦予明確的值。顯式地定義serialVersionUID有兩種用途:

在某些場合,希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有相同的serialVersionUID;在某些場合,不希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有不同的serialVersionUID。

當(dāng)你序列化了一個類實例后,希望更改一個字段或添加一個字段,不設(shè)置serialVersionUID,所做的任何更改都將導(dǎo)致無法反序化舊有實例,并在反序列化時拋出一個異常。如果你添加了serialVersionUID,在反序列舊有實例時,新添加或更改的字段值將設(shè)為初始化值(對象為null,基本類型為相應(yīng)的初始默認(rèn)值),字段被刪除將不設(shè)置。

相關(guān)注意事項:

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

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

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

詳細(xì)描述:

序列化的過程就是對象寫入字節(jié)流和從字節(jié)流中讀取對象。將對象狀態(tài)轉(zhuǎn)換成字節(jié)流之后,可以用java.io包中的各種字節(jié)流類將其保存到文件中,管道到另一 線程中或通過網(wǎng)絡(luò)連接將對象數(shù)據(jù)發(fā)送到另一主機。對象序列化功能非常簡單、強大,在RMI、Socket、JMS、EJB都有應(yīng)用。對象序列化問題在網(wǎng)絡(luò) 編程中并不是最激動人心的課題,但卻相當(dāng)重要,具有許多實用意義。

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

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

從上面的敘述中,我們知道了對象序列化是java編程中的必備武器,那么讓我們從基礎(chǔ)開始,好好學(xué)習(xí)一下它的機制和用法。

java序列化比較簡單,通常不需要編寫保存和恢復(fù)對象狀態(tài)的定制代碼。實現(xiàn)java.io.Serializable接口的類對象可以轉(zhuǎn)換成字節(jié)流或從 字節(jié)流恢復(fù),不需要在類中增加任何代碼。只有極少數(shù)情況下才需要定制代碼保存或恢復(fù)對象狀態(tài)。這里要注意:不是每個類都可序列化,有些類是不能序列化的, 例如涉及線程的類與特定JVM有非常復(fù)雜的關(guān)系。

序列化機制:

序列化分為兩大部分:序列化和反序列化。序列化是這個過程的第一部分,將數(shù)據(jù)分解成字節(jié)流,以便存儲在文件中或在網(wǎng)絡(luò)上傳輸。反序列化就是打開字節(jié)流并重構(gòu)對象。對象序列化不僅要將基本數(shù)據(jù)類型轉(zhuǎn)換成字節(jié) 表示,有時還要恢復(fù)數(shù)據(jù)?;謴?fù)數(shù)據(jù)要求有恢復(fù)數(shù)據(jù)的對象實例。ObjectOutputStream中的序列化過程與字節(jié)流連接,包括對象類型和版本信 息。反序列化時,JVM用頭信息生成對象實例,然后將對象字節(jié)流中的數(shù)據(jù)復(fù)制到對象數(shù)據(jù)成員中。

處理對象流:序列化過程和反序列化過程

java.io包有兩個序列化對象的類。ObjectOutputStream負(fù)責(zé)將對象寫入字節(jié)流,ObjectInputStream從字節(jié)流重構(gòu)對象。

我們先了解ObjectOutputStream類吧。ObjectOutputStream類擴展DataOutput接口。writeObject() 方法是最重要的方法,用于對象序列化。如果對象包含其他對象的引用,則writeObject()方法遞歸序列化這些對象。每個 ObjectOutputStream維護(hù)序列化的對象引用表,防止發(fā)送同一對象的多個拷貝。(這點很重要)由于writeObject()可以序列化整 組交叉引用的對象,因此同一ObjectOutputStream實例可能不小心被請求序列化同一對象。這時,進(jìn)行反引用序列化,而不是再次寫入對象字節(jié)流。

// 序列化 today’s date 到一個文件中.

FileOutputStream f = new FileOutputStream(“tmp”); //創(chuàng)建一個包含恢復(fù)對象(即對象進(jìn)行反序列化信息)的”tmp”數(shù)據(jù)文件

ObjectOutputStream s = new ObjectOutputStream(f);

s.writeObject(“Today”); //寫入字符串對象;

s.writeObject(new Date()); //寫入瞬態(tài)對象;

s.flush();

現(xiàn)在,讓我們來了解ObjectInputStream這個類。它與ObjectOutputStream相似。它擴展DataInput接口。 ObjectInputStream中的方法鏡像DataInputStream中讀取Java基本數(shù)據(jù)類型的公開方法。readObject()方法從 字節(jié)流中反序列化對象。每次調(diào)用readObject()方法都返回流中下一個Object。對象字節(jié)流并不傳輸類的字節(jié)碼,而是包括類名及其簽名。 readObject()收到對象時,JVM裝入頭中指定的類。如果找不到這個類,則readObject()拋出 ClassNotFoundException,如果需要傳輸對象數(shù)據(jù)和字節(jié)碼,則可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化過程。

//從文件中反序列化 string 對象和 date 對象

FileInputStream in = new FileInputStream(“tmp”);

ObjectInputStream s = new ObjectInputStream(in);

String today = (String)s.readObject(); //恢復(fù)對象;

Date date = (Date)s.readObject();

定制序列化過程:

序列化通??梢宰詣油瓿?,但有時可能要對這個過程進(jìn)行控制。java可以將類聲明為serializable,但仍可手工控制聲明為static或transient的數(shù)據(jù)成員。

public class SimpleSerializableClass implements Serializable{

String sToday=”Today:”;

transient Date dtToday=new Date();

}

序 列化時,類的所有數(shù)據(jù)成員應(yīng)可序列化除了聲明為transient或static的成員。將變量聲明為transient告訴JVM我們會負(fù)責(zé)將變元序列 化。將數(shù)據(jù)成員聲明為transient后,序列化過程就無法將其加進(jìn)對象字節(jié)流中,沒有從transient數(shù)據(jù)成員發(fā)送的數(shù)據(jù)。后面數(shù)據(jù)反序列化時, 要重建數(shù)據(jù)成員(因為它是類定義的一部分),但不包含任何數(shù)據(jù),因為這個數(shù)據(jù)成員不向流中寫入任何數(shù)據(jù)。記住,對象流不序列化static或 transient。我們的類要用writeObject()與readObject()方法以處理這些數(shù)據(jù)成員。使用writeObject()與 readObject()方法時,還要注意按寫入的順序讀取這些數(shù)據(jù)成員。

//重寫writeObject()方法以便處理transient的成員。

public void writeObject(ObjectOutputStream outputStream) throws IOException{

outputStream.defaultWriteObject();//使定制的writeObject()方法可以利用自動序列化中內(nèi)置的邏輯。

outputStream.writeObject(oSocket.getInetAddress());

outputStream.writeInt(oSocket.getPort());

}

//重寫readObject()方法以便接收transient的成員。

private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{

inputStream.defaultReadObject();//defaultReadObject()補充自動序列化

InetAddress oAddress=(InetAddress)inputStream.readObject();

int iPort =inputStream.readInt();

oSocket = new Socket(oAddress,iPort);

iID=getID();

dtToday =new Date();

}

完全定制序列化過程:

如果一個類要完全負(fù)責(zé)自己的序列化,則實現(xiàn)Externalizable接口而不是Serializable接口。Externalizable接口定義包 括兩個方法writeExternal()與readExternal()。利用這些方法可以控制對象數(shù)據(jù)成員如何寫入字節(jié)流.類實現(xiàn) Externalizable時,頭寫入對象流中,然后類完全負(fù)責(zé)序列化和恢復(fù)數(shù)據(jù)成員,除了頭以外,根本沒有自動序列化。這里要注意了。聲明類實現(xiàn) Externalizable接口會有重大的安全風(fēng)險。writeExternal()與readExternal()方法聲明為public,惡意類可 以用這些方法讀取和寫入對象數(shù)據(jù)。如果對象包含敏感信息,則要格外小心。這包括使用安全套接或加密整個字節(jié)流。到此為至,我們學(xué)習(xí)了序列化的基礎(chǔ)部分知識。

以下來源于J2EE API:

對象的默認(rèn)序列化機制寫入的內(nèi)容是:對象的類,類簽名,以及非瞬態(tài)和非靜態(tài)字段的值。其他對象的引用(瞬態(tài)和靜態(tài)字段除外)也會導(dǎo)致寫入那些對象。可使用引用共享機制對單個對象的多個引用進(jìn)行編碼,這樣即可將對象的圖形還原為最初寫入它們時的形狀。

例如,要寫入可通過 ObjectInputStream 中的示例讀取的對象,請執(zhí)行以下操作:

FileOutputStream fos = new FileOutputStream(“t.tmp”);

ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeInt(12345);

oos.writeObject(“Today”);

oos.writeObject(new Date());

oos.close();

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

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

private void writeObject(java.io.ObjectOutputStream stream) throws IOException;

writeObject 方法負(fù)責(zé)寫入特定類的對象狀態(tài),以便相應(yīng)的 readObject 方法可以還原它。該方法本身不必與屬于對象的超類或子類的狀態(tài)有關(guān)。狀態(tài)是通過使用 writeObject 方法或使用 DataOutput 支持的用于基本數(shù)據(jù)類型的方法將各個字段寫入 ObjectOutputStream 來保存的。

序列化操作不寫出沒有實現(xiàn) java.io.Serializable 接口的任何對象的字段。不可序列化的 Object 的子類可以是可序列化的。在此情況下,不可序列化的類必須有一個無參數(shù)構(gòu)造方法,以便允許初始化其字段。在此情況下,子類負(fù)責(zé)保存和還原不可序列化的類的 狀態(tài)。經(jīng)常出現(xiàn)的情況是,該類的字段是可訪問的(public、package 或 protected),或者存在可用來還原狀態(tài)的 get 和 set 方法。

實現(xiàn) writeObject 和 readObject 方法可以阻止對象的序列化,這時拋出 NotSerializableException。ObjectOutputStream 導(dǎo)致發(fā)生異常并中止序列化進(jìn)程。

實 現(xiàn) Externalizable 接口允許對象假定可以完全控制對象的序列化形式的內(nèi)容和格式。調(diào)用 Externalizable 接口的方法(writeExternal 和 readExternal)來保存和恢復(fù)對象的狀態(tài)。通過類實現(xiàn)時,它們可以使用 ObjectOutput 和 ObjectInput 的所有方法讀寫它們自己的狀態(tài)。對象負(fù)責(zé)處理出現(xiàn)的任何版本控制。

Enum 常量的序列化不同于普通的 serializable 或 externalizable 對象。enum 常量的序列化形式只包含其名稱;常量的字段值不被傳送。為了序列化 enum 常量,ObjectOutputStream 需要寫入由常量的名稱方法返回的字符串。與其他 serializable 或 externalizable 對象一樣,enum 常量可以作為序列化流中后續(xù)出現(xiàn)的 back 引用的目標(biāo)。用于序列化 enum 常量的進(jìn)程不可定制;在序列化期間,由 enum 類型定義的所有類特定的 writeObject 和 writeReplace 方法都將被忽略。類似地,任何 serialPersistentFields 或 serialVersionUID 字段聲明也將被忽略,所有 enum 類型都有一個 0L 的固定的 serialVersionUID。

基本數(shù)據(jù)(不包括 serializable 字段和 externalizable 數(shù)據(jù))以塊數(shù)據(jù)記錄的形式寫入 ObjectOutputStream 中。塊數(shù)據(jù)記錄由頭部和數(shù)據(jù)組成。塊數(shù)據(jù)部分包括標(biāo)記和跟在部分后面的字節(jié)數(shù)。連續(xù)的基本寫入數(shù)據(jù)被合并在一個塊數(shù)據(jù)記錄中。塊數(shù)據(jù)記錄的分塊因子為 1024 字節(jié)。每個塊數(shù)據(jù)記錄都將填滿 1024 字節(jié),或者在終止塊數(shù)據(jù)模式時被寫入。調(diào)用 ObjectOutputStream 方法 writeObject、defaultWriteObject 和 writeFields 最初只是終止所有現(xiàn)有塊數(shù)據(jù)記錄。

更多信息請查看IT技術(shù)專欄

更多信息請查看網(wǎng)絡(luò)編程
易賢網(wǎng)手機網(wǎng)站地址:Java序列化(Serializable)與反序列化
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國考·省考課程試聽報名

  • 報班類型
  • 姓名
  • 手機號
  • 驗證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 新媒體/短視頻平臺 | 手機站點 | 投訴建議
工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
咨詢QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報警專用圖標(biāo)