Java 序列化和反序列化的底層原理

序列化和反序列化

序列化是經過某種算法將存儲於內存中的對象轉換成能夠用於持久化存儲或者通訊的形式的過程前端

反序列化是將這種被持久化存儲或者通訊的數據經過對應解析算法還原成對象的過程,它是序列化的逆向操做算法

爲何須要序列化

前端請求後端接口數據的時候,後端須要返回 JSON 數據,這就是後端將 Java 堆中的對象序列化爲了 JSON 數據傳給前端,前端能夠根據自身需求直接使用或者將其反序列化爲 JS 對象後端

RPC 遠程調用過程當中,調用者和被調用者必須約定好序列化和反序列化算法,好比 A 應用將 User 對象序列化爲了 JSON 數據傳給 B 應用,User 對象數據爲 {"id": 1, "name": "long"},到達 B 應用的時候須要將這些數據反序列化爲對象,若是此時 B 應用的反序列化算法是 XML 的話那麼確定就解析失敗了,因此必須都得約定好他們都採用 JSON 序列化算法,那麼基於 JSON 標準就能成功解析出 User 對象數組

Java 中的序列化

transient

若是某個字段咱們不想經過 Java 默認序列化機制輸出,咱們就能夠經過該字段來代表當前字段不須要被序列化性能

writeObject 和 readObject

咱們想經過自定義的方式將 address 數據序列化優化

其中的 writeObject 做用於寫序列化數據的時候會反射調用該方法,readObject 會在反序列化的時候調用3d

serialVersionUID 的做用

若是咱們沒有自定義 serialVersionUID 的話,會根據當前類的信息自動生成,若是當前類沒有作修改那麼生成的 serialVersionUID 是一致的,若是修改後 serialVersionUID 就會改變致使沒法反序列化,因此在平常使用中咱們必定要填寫該字段code

Java 序列化的實現原理

最開始看代碼的時候,不要一下就陷入所有細節,咱們應該只看咱們目前關注的點,當認識逐漸深入以後再來看一些細節,否則的話容易看的一臉懵逼 cdn

首先調用objectOutputStream.writeObject(user); 而後調用 writeObject0(obj, false); 在這個方法裏面有這樣一段代碼對象

這裏能夠看到若是咱們要序列化的是一個對象而且它沒有實現 Serializable 接口的話就會直接拋出 NotSerializableException,因爲咱們目前傳入的是 User 對象它實現了 Serializable 因此會進入到 writeOrdinaryObject 中

首先將 TC_OBJECT 這一個對象標誌位寫入到流中,標識着當前開始寫一個的數據是一個對象,而後調用 writeSerialData 開始寫入具體數據

  1. 首先會得到 ClassDataSlot 咱們能夠把它看作是提供了序列化對象的輔助手段

  2. 在此經過 ClassDataSlot 去檢查序列化對象中是否實現了 writeObject 這個方法,那麼這個 writeObjectMethod 是在何時初始化的呢?立刻會講到

  3. 若是實現了 writeObject 咱們就去反射調用該方法

  4. 若是當前類沒有實現 writeObject 方法就調用默認的 defaultWriteFields 去寫數據

如何知道對象是否實現了 writeObject(ObjectOutputStream out) 和 readObject

在上文咱們知道是經過 writeObjectMethod 這個來判斷的,那麼這個字段是在哪裏初始化的呢,咱們回到 ObjectOutputStream 的 writeObject0 方法,在調用後續的 writeOrderinaryObject 方法以前有這樣一段代碼

而後會調用到這段代碼

在建立 ObjectStreamClass 對象的過程當中會經過反射去拿到當前類的方法,而後根據方法名 writeObject 和參數 ObjectOutputStream 去判斷有沒有這個方法,有的話就返回沒有就返回爲 null

而後咱們回到 defaultWriteFields(Object obj, ObjectStreamClass desc) 繼續來看

  1. 拿到當前須要寫入數據的具體長度
  2. 經過反射去獲取當前數據的值,ObjectStreamClass desc 這個對象多是個 Object 多是基本類型等,此時拿到的是 User 對象,因此取到的值默認爲空,由於這裏只是寫入它的具體字段的數據
  3. 經過反射拿到當前對象的全部的值
  4. 挨個調用 writeObject0 寫入具體的值,首先調用的是 Integer 因爲它是一個包裝類,Integer 繼承了 Number,Number 類實現了 Serializable 因此會和 User 對象走同樣的流程到達次數(能夠本身 DEBUG 一下)而後拆包取出值調用
  5. 隨後就進入到了 String 的寫入,再次調用 writeObject0,又到達 ObjectOutputStream 的 writeObject0,後續就有所區別了由於這裏寫入的具體類型是 String

由於 Integer 也實現了 Serializable 而且這裏沒有針對他坐特殊處理,因此它會走 writeOrdinaryObject,而 String 這裏判斷了,須要去調用 writeString
這個方法也比較簡單首先寫入 String 標誌位而後寫入具體的數據和長度,其它的類型也是同樣會走這裏不一樣的分支,最終將數據寫入到流中

最後來看一下序列化後佔用了多少個字節

Java 反序列化的原理

反序列化其實就是序列化的逆向過程,若是你看懂了序列化的關鍵代碼,那麼看這個過程就不會很難,下面貼出關鍵代碼作出分析

這裏可以看到會根據反序列對象的具體類型分別作不一樣的處理,咱們當前的對象是 User 對象因此會進入箭頭指向的方法

在該方法中會去建立一個實例對象,其中 isInstantiable 這個方法是去判斷構造器是否初始化了,同時這裏還會將 writeObject 和 readObject 方法設置好,而後會經過 hasReadResolveMethod 方法來肯定是否實現了 readObject 方法若是實現了就反射調用 readObject 方法

在調用了 readSerialData 方法以後會調用 defaultReadFields 方法來設置字段的值,當前的 Obj 是 User 對象

  1. 獲取當前傳入對象數據長度因爲傳入的是 User 空對象,因此此時長度爲空
  2. 反射獲取到須要被反序列化的全部字段,而且建立對應的數組來保存對應的值,此時獲取到 User 對象有 2 個字段 id 和 name
  3. 而後開始遞歸調用 readObject0 處理完全部須要被反序列化的字段,就一當前的 id 和 name 舉例
    • 和上文序列化同樣 id 是 Integer 包裝類因此會被識別爲 Object 當再次到達這個方法的時候,在第一步 primDataSize 數據長度爲 4 由於是 int 類型 4 個字節
    • 到第二步,由於是 Integer 沒有其它須要被反序列化的字段它只有自己的拆包後的值,因此會到達第四步設置當前 id 的值
  4. 設置當前基本類型字段的值

對於 String 類型來講,在反序列化第一張中會調用讀取對應的值

其它的序列化方式

一個新的技術的誕生都是有必定的緣由和背景的,好比說 Java 原生序列化後數據比較大,傳輸效率低,同時又又沒法跨語言通訊,因此不少人選擇使用 XML 的來序列化數據,XML 序列化後卻是解決了跨語言通訊的問題,可是它序列化後的數據比原生數據還要大,因此就誕生了 JSON 序列化,他支持跨語言,而且序列化後的數據遠遠小於前 2 者,最後有人想進一步的優化大小就引入了 Protobuf 它具有 壓縮的功能,被壓縮的數據小於 JSON 序列化後的數據。

其它的序列化方式

  • XML
  • JSON
    • Jackson
    • FastJson
  • Hessian
  • thrift
  • protobuf
  • ...

後面會寫一篇文章就會來聊聊其它的序列化方式對比下他們的性能和底層使用原理

相關文章
相關標籤/搜索