面試官:您能說(shuō)說(shuō)序列化和反序列化嗎?是怎么實(shí)現(xiàn)的?什么場(chǎng)景下需要它?
作者:xcbeyond
瘋狂源自夢(mèng)想,技術(shù)成就輝煌!微信公眾號(hào):《程序猿技術(shù)大咖》號(hào)主,專(zhuān)注后端開(kāi)發(fā)多年,擁有豐富的研發(fā)經(jīng)驗(yàn),樂(lè)于技術(shù)輸出、分享,現(xiàn)階段從事微服務(wù)架構(gòu)項(xiàng)目的研發(fā)工作,涉及架構(gòu)設(shè)計(jì)、技術(shù)選型、業(yè)務(wù)研發(fā)等工作。對(duì)于Java、微服務(wù)、數(shù)據(jù)庫(kù)、Docker有深入了解,并有大量的調(diào)優(yōu)經(jīng)驗(yàn)。
序列化和反序列化是Java中最基礎(chǔ)的知識(shí)點(diǎn),也是很容易被大家遺忘的,雖然天天使用它,但并不一定都能清楚的說(shuō)明白。我相信很多小伙伴們掌握的也就幾句概念、關(guān)鍵字(Serializable)而已,如果深究問(wèn)一下序列化和反序列化是如何實(shí)現(xiàn)、使用場(chǎng)景等,就可能不知所措了。在每次我作為面試官,考察Java基礎(chǔ)時(shí),通常都會(huì)問(wèn)到序列化、反序列化的知識(shí)點(diǎn),用以衡量其Java基礎(chǔ)如何。當(dāng)被問(wèn)及Java序列化是什么?反序列化是什么?什么場(chǎng)景下會(huì)用到?如果不用它,會(huì)出現(xiàn)什么問(wèn)題等,一般大家回答也就是幾句簡(jiǎn)單的概念而已,有的工作好幾年的應(yīng)聘者甚至連概念都說(shuō)不清楚,一臉悶逼。
本文就序列化和反序列化展開(kāi)深入的探討,當(dāng)被別人問(wèn)及時(shí),不至于一臉悶逼、尷尬,或許會(huì)為你以后的求職面試中增加一點(diǎn)點(diǎn)籌碼。
一、基本概念
1、什么是序列化和反序列化
序列化是指將Java對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程,而反序列化則是將字節(jié)序列轉(zhuǎn)換為Java對(duì)象的過(guò)程。
Java對(duì)象序列化是將實(shí)現(xiàn)了Serializable接口的對(duì)象轉(zhuǎn)換成一個(gè)字節(jié)序列,能夠通過(guò)網(wǎng)絡(luò)傳輸、文件存儲(chǔ)等方式傳輸 ,傳輸過(guò)程中卻不必?fù)?dān)心數(shù)據(jù)在不同機(jī)器、不同環(huán)境下發(fā)生改變,也不必關(guān)心字節(jié)的順序或其他任何細(xì)節(jié),并能夠在以后將這個(gè)字節(jié)序列完全恢復(fù)為原來(lái)的對(duì)象(恢復(fù)這一過(guò)程稱之為反序列化)。
對(duì)象的序列化是非常有趣的,因?yàn)槔盟梢詫?shí)現(xiàn)輕量級(jí)持久性,“持久性”意味著一個(gè)對(duì)象的生存周期不單單取決于程序是否正在運(yùn)行,它可以生存于程序的調(diào)用之間。通過(guò)將一個(gè)序列化對(duì)象寫(xiě)入磁盤(pán),然后在重新調(diào)用程序時(shí)恢復(fù)該對(duì)象,從而達(dá)到實(shí)現(xiàn)對(duì)象的持久性的效果。
本質(zhì)上講,序列化就是把實(shí)體對(duì)象狀態(tài)按照一定的格式寫(xiě)入到有序字節(jié)流,反序列化就是從有序字節(jié)流重建對(duì)象,恢復(fù)對(duì)象狀態(tài)。
2、為什么需要使用序列化和反序列化
我們知道,不同進(jìn)程/程序間進(jìn)行遠(yuǎn)程通信時(shí),可以相互發(fā)送各種類(lèi)型的數(shù)據(jù),包括文本、圖片、音頻、視頻等,而這些數(shù)據(jù)都會(huì)以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。
那么當(dāng)兩個(gè)Java進(jìn)程進(jìn)行通信時(shí),能否實(shí)現(xiàn)進(jìn)程間的對(duì)象傳送呢?當(dāng)然是可以的!如何做到呢?這就需要使用Java序列化與反序列化了。發(fā)送方需要把這個(gè)Java對(duì)象轉(zhuǎn)換為字節(jié)序列,然后在網(wǎng)絡(luò)上傳輸,接收方則需要將字節(jié)序列中恢復(fù)出Java對(duì)象。
我們清楚了為什么需要使用Java序列化和反序列化后,我們很自然地會(huì)想到Java序列化有哪些好處:
實(shí)現(xiàn)了數(shù)據(jù)的持久化,通過(guò)序列化可以把數(shù)據(jù)永久地保存到硬盤(pán)上(如:存儲(chǔ)在文件里),實(shí)現(xiàn)永久保存對(duì)象。
利用序列化實(shí)現(xiàn)遠(yuǎn)程通信,即:能夠在網(wǎng)絡(luò)上傳輸對(duì)象。
二、如何實(shí)現(xiàn)Java序列化和反序列化
只要對(duì)象實(shí)現(xiàn)了Serializable、Externalizable接口(該接口僅僅是一個(gè)標(biāo)記接口,并不包含任何方法),則該對(duì)象就實(shí)現(xiàn)了序列化。
- 1
1、具體是如何實(shí)現(xiàn)的呢?
序列化,首先要?jiǎng)?chuàng)建某些OutputStream對(duì)象,然后將其封裝在一個(gè)ObjectOutputStream對(duì)象內(nèi),這時(shí)調(diào)用writeObject()方法,即可將對(duì)象序列化,并將其發(fā)送給OutputStream(對(duì)象序列化是基于字節(jié)的,因此使用的InputStream和OutputStream繼承的類(lèi))。
反序列化,即反向進(jìn)行序列化的過(guò)程,需要將一個(gè)InputStream封裝在ObjectInputStream對(duì)象內(nèi),然后調(diào)用readObject()方法,獲得一個(gè)對(duì)象引用(它是指向一個(gè)向上轉(zhuǎn)型的Object),然后進(jìn)行類(lèi)型強(qiáng)制轉(zhuǎn)換來(lái)得到該對(duì)象。
假定一個(gè)User類(lèi),它的對(duì)象需要序列化,可以有如下三種方法:
(1)若User類(lèi)僅僅實(shí)現(xiàn)了Serializable接口,則可以按照以下方式進(jìn)行序列化和反序列化。
ObjectOutputStream采用默認(rèn)的序列化方式,對(duì)User對(duì)象的非transient的實(shí)例變量進(jìn)行序列化。
ObjcetInputStream采用默認(rèn)的反序列化方式,對(duì)對(duì)User對(duì)象的非transient的實(shí)例變量進(jìn)行反序列化。
(2)若User類(lèi)僅僅實(shí)現(xiàn)了Serializable接口,并且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則采用以下方式進(jìn)行序列化與反序列化。
ObjectOutputStream調(diào)用User對(duì)象的writeObject(ObjectOutputStream out)的方法進(jìn)行序列化。
ObjectInputStream會(huì)調(diào)用User對(duì)象的readObject(ObjectInputStream in)的方法進(jìn)行反序列化。
(3)若User類(lèi)實(shí)現(xiàn)了Externalnalizable接口,且User類(lèi)必須實(shí)現(xiàn)readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進(jìn)行序列化與反序列化。
ObjectOutputStream調(diào)用User對(duì)象的writeExternal(ObjectOutput out))的方法進(jìn)行序列化。
ObjectInputStream會(huì)調(diào)用User對(duì)象的readExternal(ObjectInput in)的方法進(jìn)行反序列化。
java.io.ObjectOutputStream:對(duì)象輸出流,它的writeObject(Object obj)方法可以對(duì)指定的obj對(duì)象進(jìn)行序列化,把得到的字節(jié)序列寫(xiě)到一個(gè)目標(biāo)輸出流中。
java.io.ObjectInputStream:對(duì)象輸入流,它的readObject()方法可以將從輸入流中讀取字節(jié)序列,再把它們反序列化成為一個(gè)對(duì)象,并將其返回。
2、序列化和反序列化示例
為了更好的理解序列化和反序列化的過(guò)程,舉例如下:
public class SerialDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化對(duì)象User
FileOutputStream fos = new FileOutputStream("object.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user1 = new User("xcbeyond", "123456789");
oos.writeObject(user1);
oos.flush();
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream("object.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user2 = (User) ois.readObject();
System.out.println(user2.getUsername()+ "," + user2.getPassword());
}
}
// 對(duì)象User,對(duì)其實(shí)現(xiàn)了Serializable接口
public class User implements Serializable {
private String username;
private String password;
……
}
3、什么場(chǎng)景下需要序列化
當(dāng)你想把的內(nèi)存中的對(duì)象狀態(tài)保存到一個(gè)文件中或者數(shù)據(jù)庫(kù)中時(shí)候。
當(dāng)你想用套接字在網(wǎng)絡(luò)上傳送對(duì)象的時(shí)候。
當(dāng)你想通過(guò)RMI傳輸對(duì)象的時(shí)候。
三、注意事項(xiàng)
1、當(dāng)一個(gè)父類(lèi)實(shí)現(xiàn)序列化,子類(lèi)就會(huì)自動(dòng)實(shí)現(xiàn)序列化,不需要顯式實(shí)現(xiàn)Serializable接口。
2、當(dāng)一個(gè)對(duì)象的實(shí)例變量引用其他對(duì)象,序列化該對(duì)象時(shí)也把引用對(duì)象進(jìn)行序列化。
3、并非所有的對(duì)象都可以進(jìn)行序列化,比如:
安全方面的原因,比如一個(gè)對(duì)象擁有private,public等成員變量,對(duì)于一個(gè)要傳輸?shù)膶?duì)象,比如寫(xiě)到文件,或者進(jìn)行RMI傳輸?shù)鹊?,在序列化進(jìn)行傳輸?shù)倪^(guò)程中,這個(gè)對(duì)象的private等域是不受保護(hù)的;
資源分配方面的原因,比如socket,thread類(lèi),如果可以序列化,進(jìn)行傳輸或者保存,也無(wú)法對(duì)他們進(jìn)行重新的資源分配,而且,也是沒(méi)有必要這樣實(shí)現(xiàn)。
4、聲明為static和transient類(lèi)型的成員變量不能被序列化。因?yàn)閟tatic代表類(lèi)的狀態(tài),transient代表對(duì)象的臨時(shí)數(shù)據(jù)。
5、序列化運(yùn)行時(shí)會(huì)使用一個(gè)稱為 serialVersionUID 的版本號(hào),并與每個(gè)可序列化的類(lèi)相關(guān)聯(lián),該序列號(hào)在反序列化過(guò)程中用于驗(yàn)證序列化對(duì)象的發(fā)送者和接收者是否為該對(duì)象加載了與序列化兼容的類(lèi)。如果接收者加載的該對(duì)象的類(lèi)的 serialVersionUID 與對(duì)應(yīng)的發(fā)送者的類(lèi)的版本號(hào)不同,則反序列化將會(huì)導(dǎo)致 InvalidClassException??尚蛄谢?lèi)可以通過(guò)聲明名為 “serialVersionUID” 的字段(該字段必須是靜態(tài) (static)、最終 (final) 的 long 型字段)顯式聲明其自己的 serialVersionUID。
如果序列化的類(lèi)未顯式的聲明 serialVersionUID,則序列化運(yùn)行時(shí)將基于該類(lèi)的各個(gè)方面計(jì)算該類(lèi)的默認(rèn) serialVersionUID 值,如“Java(TM) 對(duì)象序列化規(guī)范”中所述。不過(guò),強(qiáng)烈建議 所有可序列化類(lèi)都顯式聲明 serialVersionUID 值,原因是計(jì)算默認(rèn)的 serialVersionUID 對(duì)類(lèi)的詳細(xì)信息具有較高的敏感性,根據(jù)編譯器實(shí)現(xiàn)的不同可能千差萬(wàn)別,這樣在反序列化過(guò)程中可能會(huì)導(dǎo)致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 java 編譯器實(shí)現(xiàn)的一致性,序列化類(lèi)必須聲明一個(gè)明確的 serialVersionUID 值。還強(qiáng)烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應(yīng)用于直接聲明類(lèi) -- serialVersionUID 字段作為繼承成員沒(méi)有用處。數(shù)組類(lèi)不能聲明一個(gè)明確的 serialVersionUID,因此它們總是具有默認(rèn)的計(jì)算值,但是數(shù)組類(lèi)沒(méi)有匹配 serialVersionUID 值的要求。
- 1
6、Java有很多基礎(chǔ)類(lèi)已經(jīng)實(shí)現(xiàn)了serializable接口,比如String,Vector等。但是也有一些沒(méi)有實(shí)現(xiàn)serializable接口的。
7、如果一個(gè)對(duì)象的成員變量是一個(gè)對(duì)象,那么這個(gè)對(duì)象的數(shù)據(jù)成員也會(huì)被保存!這是能用序列化解決深拷貝的重要原因。
有了上面關(guān)于序列化和反序列化的詳細(xì)介紹,現(xiàn)在你對(duì)平時(shí)所用的序列化和反序列化是如何實(shí)現(xiàn)的,什么場(chǎng)景下會(huì)使用它,是不是更加深刻了吧
- 1
參考:
(美) Bruce Eckel 著 陳昊鵬 譯 《Java編程思想》
https://blog.csdn.net/xlgen157387/article/details/79840134
https://blog.csdn.net/Mr_EvanChen/article/details/79724426