面試官:您能說說序列化和反序列化嗎?是怎麼實現的?什麼場景下需要它?
- 2019 年 10 月 6 日
- 筆記
序列化和反序列化是Java中最基礎的知識點,也是很容易被大家遺忘的,雖然天天使用它,但並不一定都能清楚的說明白。我相信很多小夥伴們掌握的也就幾句概念、關鍵字(Serializable)而已,如果深究問一下序列化和反序列化是如何實現、使用場景等,就可能不知所措了。
在每次我作為面試官,考察Java基礎時,通常都會問到序列化、反序列化的知識點,用以衡量其Java基礎如何。當被問及Java序列化是什麼?反序列化是什麼?什麼場景下會用到?如果不用它,會出現什麼問題等,一般大家回答也就是幾句簡單的概念而已,有的工作好幾年的應聘者甚至連概念都說不清楚,一臉悶逼。
本文就序列化和反序列化展開深入的探討,當被別人問及時,不至於一臉悶逼、尷尬,或許會為你以後的求職面試中增加一點點籌碼。
一、基本概念
1、什麼是序列化和反序列化
序列化是指將Java對象轉換為位元組序列的過程,而反序列化則是將位元組序列轉換為Java對象的過程。
Java對象序列化是將實現了Serializable介面的對象轉換成一個位元組序列,能夠通過網路傳輸、文件存儲等方式傳輸 ,傳輸過程中卻不必擔心數據在不同機器、不同環境下發生改變,也不必關心位元組的順序或其他任何細節,並能夠在以後將這個位元組序列完全恢復為原來的對象(恢復這一過程稱之為反序列化)。
對象的序列化是非常有趣的,因為利用它可以實現輕量級持久性,「持久性」意味著一個對象的生存周期不單單取決於程式是否正在運行,它可以生存於程式的調用之間。通過將一個序列化對象寫入磁碟,然後在重新調用程式時恢復該對象,從而達到實現對象的持久性的效果。
本質上講,序列化就是把實體對象狀態按照一定的格式寫入到有序位元組流,反序列化就是從有序位元組流重建對象,恢復對象狀態。
2、為什麼需要使用序列化和反序列化
我們知道,不同進程/程式間進行遠程通訊時,可以相互發送各種類型的數據,包括文本、圖片、音頻、影片等,而這些數據都會以二進位序列的形式在網路上傳送。
那麼當兩個Java進程進行通訊時,能否實現進程間的對象傳送呢?當然是可以的!如何做到呢?這就需要使用Java序列化與反序列化了。發送方需要把這個Java對象轉換為位元組序列,然後在網路上傳輸,接收方則需要將位元組序列中恢復出Java對象。
我們清楚了為什麼需要使用Java序列化和反序列化後,我們很自然地會想到Java序列化有哪些好處:
- 實現了數據的持久化,通過序列化可以把數據永久地保存到硬碟上(如:存儲在文件里),實現永久保存對象。
- 利用序列化實現遠程通訊,即:能夠在網路上傳輸對象。
二、如何實現Java序列化和反序列化
只要對象實現了Serializable、Externalizable介面(該介面僅僅是一個標記介面,並不包含任何方法),則該對象就實現了序列化。
1、具體是如何實現的呢?
序列化,首先要創建某些OutputStream對象,然後將其封裝在一個ObjectOutputStream對象內,這時調用writeObject()方法,即可將對象序列化,並將其發送給OutputStream(對象序列化是基於位元組的,因此使用的InputStream和OutputStream繼承的類)。

反序列化,即反向進行序列化的過程,需要將一個InputStream封裝在ObjectInputStream對象內,然後調用readObject()方法,獲得一個對象引用(它是指向一個向上轉型的Object),然後進行類型強制轉換來得到該對象。

假定一個User類,它的對象需要序列化,可以有如下三種方法:
(1)若User類僅僅實現了Serializable介面,則可以按照以下方式進行序列化和反序列化。
- ObjectOutputStream採用默認的序列化方式,對User對象的非transient的實例變數進行序列化。
- ObjcetInputStream採用默認的反序列化方式,對對User對象的非transient的實例變數進行反序列化。
(2)若User類僅僅實現了Serializable介面,並且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則採用以下方式進行序列化與反序列化。
- ObjectOutputStream調用User對象的writeObject(ObjectOutputStream out)的方法進行序列化。
- ObjectInputStream會調用User對象的readObject(ObjectInputStream in)的方法進行反序列化。
(3)若User類實現了Externalnalizable介面,且User類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。
- ObjectOutputStream調用User對象的writeExternal(ObjectOutput out))的方法進行序列化。
- ObjectInputStream會調用User對象的readExternal(ObjectInput in)的方法進行反序列化。
java.io.ObjectOutputStream:對象輸出流,它的writeObject(Object obj)方法可以對指定的obj對象進行序列化,把得到的位元組序列寫到一個目標輸出流中。 java.io.ObjectInputStream:對象輸入流,它的readObject()方法可以將從輸入流中讀取位元組序列,再把它們反序列化成為一個對象,並將其返回。
2、序列化和反序列化示例
為了更好的理解序列化和反序列化的過程,舉例如下:
public class SerialDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { // 序列化對象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()); } }
// 對象User,對其實現了Serializable介面 public class User implements Serializable { private String username; private String password; …… }
3、什麼場景下需要序列化
- 當你想把的記憶體中的對象狀態保存到一個文件中或者資料庫中時候。
- 當你想用套接字在網路上傳送對象的時候。
- 當你想通過RMI傳輸對象的時候。
三、注意事項
1、當一個父類實現序列化,子類就會自動實現序列化,不需要顯式實現Serializable介面。
2、當一個對象的實例變數引用其他對象,序列化該對象時也把引用對象進行序列化。
3、並非所有的對象都可以進行序列化,比如:
安全方面的原因,比如一個對象擁有private,public等成員變數,對於一個要傳輸的對象,比如寫到文件,或者進行RMI傳輸等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的;
資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者保存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現。
4、聲明為static和transient類型的成員變數不能被序列化。因為static代表類的狀態,transient代表對象的臨時數據。
5、序列化運行時會使用一個稱為 serialVersionUID 的版本號,並與每個可序列化的類相關聯,該序列號在反序列化過程中用於驗證序列化對象的發送者和接收者是否為該對象載入了與序列化兼容的類。如果接收者載入的該對象的類的 serialVersionUID 與對應的發送者的類的版本號不同,則反序列化將會導致 InvalidClassException。可序列化類可以通過聲明名為 "serialVersionUID" 的欄位(該欄位必須是靜態 (static)、最終 (final) 的 long 型欄位)顯式聲明其自己的 serialVersionUID。
如果序列化的類未顯式的聲明 serialVersionUID,則序列化運行時將基於該類的各個方面計算該類的默認 serialVersionUID 值,如「Java(TM) 對象序列化規範」中所述。不過,強烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因是計算默認的 serialVersionUID 對類的詳細資訊具有較高的敏感性,根據編譯器實現的不同可能千差萬別,這樣在反序列化過程中可能會導致意外的 InvalidClassException。因此,為保證 serialVersionUID 值跨不同 java 編譯器實現的一致性,序列化類必須聲明一個明確的 serialVersionUID 值。還強烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應用於直接聲明類 — serialVersionUID 欄位作為繼承成員沒有用處。數組類不能聲明一個明確的 serialVersionUID,因此它們總是具有默認的計算值,但是數組類沒有匹配 serialVersionUID 值的要求。
6、Java有很多基礎類已經實現了serializable介面,比如String,Vector等。但是也有一些沒有實現serializable介面的。
7、如果一個對象的成員變數是一個對象,那麼這個對象的數據成員也會被保存!這是能用序列化解決深拷貝的重要原因。
有了上面關於序列化和反序列化的詳細介紹,現在你對平時所用的序列化和反序列化是如何實現的,什麼場景下會使用它,是不是更加深刻了吧
參考:
- (美) Bruce Eckel 著 陳昊鵬 譯 《Java編程思想》
- https://blog.csdn.net/xlgen157387/article/details/79840134
- https://blog.csdn.net/Mr_EvanChen/article/details/79724426