1. 程式人生 > >序列化、反序列化

序列化、反序列化

1.什麼是序列化和反序列化

序列化:將物件的狀態資訊轉換成便於儲存或者傳輸的格式的過程,保證物件的完整性和可傳遞性。常見的序列化格式有位元組陣列,json字串,xml字串等。
反序列化:將位元組陣列,json字串,xml字串等轉換成物件的過程

java的序列化就是將物件轉化為位元組流,以便在不同程序或網路之間進行傳輸,而在接收方,需要以相同的方式對位元組流進行反序列化,得到傳輸的物件。jdk提供了序列化和反序列化相關實現。
Java物件被序列化之後,它的編碼就可以從一臺正在執行的虛擬機器傳送到另一臺虛擬機器上,或者被儲存到磁碟上,供以後反序列化時用。

2.應用場景

遠端通訊:
遠端通訊是以二進位制位元組流傳輸的,當需要傳輸物件的時候,首先在傳送端需要將物件序列化為二進位制形式方便網路傳輸,然後在接收端將二進位制形式反序列化為java物件。比如早期的RMI,甚至現在流行的RPC通訊都用到了序列化和反序列化。

Client 端通過 Façade Object 才可以與業務邏輯物件進行互動。而客戶端的 Façade Object 不能直接由 Client 生成,而是需要 Server 端生成,然後序列化後通過網路將二進位制物件資料傳給 Client,Client 負責反序列化得到 Façade 物件。

session序列化:
容器突然關閉,或者我們想要伺服器重啟後,還是原來的session,這就涉及到了序列化(Serializable)。序列化的功能就是添加了一個唯一的ID(類主鍵),這樣在反序列化(從硬碟載入到記憶體)的時候就可以成功找到相應的物件。

將物件儲存在session中時一定要序列化(序列化)後才可以,否則會丟失資料精度(型別和結構)。
另外如基本資料型別、String、JDK提供的一些類系統會自動正確的序列化資料。

另外,容器的關閉並不會導致session的銷燬。過程是這樣子的,一旦容器關閉後,session就會被持久化到硬碟,並沒有真正銷燬。
當伺服器停掉後,tomcat目錄下多了個SESSION.ser檔案,伺服器重啟成功後,該檔案又消失了。

  1. 容器關閉後session並沒有消失,而是被持久化到了硬盤裡;
  2. 如果專案中的POJO實現了Serializable介面,那麼會跟著session一起被持久化到硬碟,在反序列化的時候會成功還原;
  3. 要想伺服器重啟後,還是原來的session,還繼續緊接著原來的頁面操作的話,就需要例項化專案中的POJO。

3.JAVA如何序列化和反序列化一個物件?

(1)實現Serializable介面


序列化執行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,在反序列化過程中用於驗證序列化物件的傳送者和接收者是否為該物件載入了與序列化相容的類。
在類中增加writeObject 和 readObject 方法可以實現自定義序列化策略,比如對敏感資訊的加密解密場景。

如果實現了該介面但未顯示申明serialVersionUID,則序列化執行時將基於該類的各個方面計算該類的預設 serialVersionUID 值。強烈建議顯示的去申明serialVersionUID,因為編譯的實現和計算serialVersionUID的千差萬別有可能在跨編譯器編譯時生成了不同的serialVersionUID,導致反序列化的時候丟擲InvalidClassException。

(2)實現Externalizable介面
Externalizable介面繼承了Serializable介面,介面中定義了兩個方法writeExternal 和 readExternal 方法。實現了Externalizable介面的類還必須實現writeExternal 和 readExternal 方法
在使用Externalizable時相應的介面必須提供一個public的無參構造器,否則會丟擲InvalidClassException。

4.Java注意事項

1、serialVersionUID
RPC呼叫的過程中,JVM在進行反序列化時,除了判斷類路徑和功能程式碼是否一致,還會把傳來 的位元組流中的serialVersionUID與本地相應類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序 列化,否則就會出現序列化版本不一致的異常。
當一個類的物件被序列化之後,而對應的接受端如果用時老版本的class檔案,那麼物件被反序列化之後,可能會丟失一些資訊。

serialVersionUID有兩種生成方式:
一個是預設的1L,比如:private static final long serialVersionUID = 1L;預設值有個好處就是在程式碼一致時反序列化成功。如果沒有特殊需求,就是用預設的 1L 就可以
利用JDK 工具,根據類名、介面名、成員方法及屬性等來生成一個64位的雜湊欄位,比如:private static final long serialVersionUID = -6784979447075466710L;隨機生成的序列化 ID 有什麼作用呢,有些時候,通過改變序列化 ID 可以用來限制某些使用者的使用。

在類檔案中顯示加入private static final long serialVersionUID是有好處的:
即使類檔案在序列化之後已經改變,在反序列化依然可以成功。
利於JVM的移植,否則自動生成的serialVersionUID可能因為JVM的不同生成不同的UID導致版本相容問題。

2、類與變數

當一個物件的例項變數引用其他物件,序列化該物件時也把引用物件進行序列化;
內部類不能被序列化!對於內部類來說,只有靜態的內部類實現Serializable接口才可以被序列化。
單例
反射可以破壞單例模式,除了反射以外,使用序列化與反序列化也同樣會破壞單例。對Singleton的序列化與反序列化得到的物件是一個新的物件。
解決的辦法是在單例類中覆蓋readResolve方法,並在該方法中指定要返回的物件的生成策略,就可以防止單例被破壞。
變數
1.安全方面的原因,private不被序列化。
2. 資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者儲存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現。
靜態變數
序列化時,並不儲存靜態變數。
序列化儲存的是物件的狀態,static變數儲存在全域性資料區,在物件未例項化時就已經生成,屬於類的狀態,因此 序列化並不儲存靜態變數。

3、父類與自類序列化
1、當一個父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable介面;
2、反過來子類實現了Serializable,父類未實現:如果父類不實現的話的,就 需要有預設的無參的建構函式。
一個 Java 物件的構造必須先有父物件,才有子物件,反序列化也不例外。所以反序列化時,為了構造父物件,只能呼叫父類的無參建構函式作為預設的父物件。父類的屬性是直接被跳過不儲存。

4、transient關鍵字
當成員變數被transient修飾後,該變數不會被序列化。在被反序列化後,transient 變數的值被設為初始值,如 int 型的是 0,物件型的是 null。
一個典型的使用場景就是,對於某些敏感資訊不讓儲存或者傳輸的,就可以用該關鍵字修飾。

另外我們也可以將不需要被序列化的欄位抽取出來放到父類中,子類實現 Serializable 介面,父類不實現,不用重複抒寫 transient,程式碼簡潔
5、Java序列化儲存
Java 序列化機制為了節省磁碟空間,當寫入檔案的為同一物件時,並不會再將物件的內容進行儲存,而只是再次儲存一份引用。
第一次寫入物件以後,第二次再試圖寫的時候,虛擬機器根據引用關係知道已經有一個相同物件寫入檔案,因此只儲存第二次寫的引用,所以讀取時,都是第一次儲存的物件。在對一個物件多次 writeObject 需要特別注意。