Java序列化和反序列化探索

基本概念

序列化就是把一個(gè)Java對(duì)象變?yōu)樽止?jié)序列(byte[]數(shù)組)的過程。
反序列化其實(shí)就是把一個(gè)字節(jié)序列變回Java對(duì)象的過程。

對(duì)象序列化的兩種用途:

  • 1 將Java對(duì)象變?yōu)樽止?jié)序列永久的保存在硬盤上的文件里
  • 2 將Java對(duì)象變?yōu)樽止?jié)序列方便在網(wǎng)絡(luò)上傳輸

舉例:

  • 在很多應(yīng)用中,需要對(duì)某些對(duì)象進(jìn)行序列化,讓它們離開內(nèi)存空間,入住物理硬盤,以便長期保存。比如最常見的是Web服務(wù)器中的Session對(duì)象,當(dāng)有 10萬用戶并發(fā)訪問,就有可能出現(xiàn)10萬個(gè)Session對(duì)象,內(nèi)存可能吃不消,于是Web容器就會(huì)把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對(duì)象還原到內(nèi)存中。
  • 當(dāng)兩個(gè)進(jìn)程在進(jìn)行遠(yuǎn)程通信時(shí),彼此可以發(fā)送各種類型的數(shù)據(jù)。無論是何種類型的數(shù)據(jù),都會(huì)以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。發(fā)送方需要把這個(gè)Java對(duì)象轉(zhuǎn)換為字節(jié)序列,才能在網(wǎng)絡(luò)上傳送;接收方則需要把字節(jié)序列再恢復(fù)為Java對(duì)象。

JDK類庫中的序列化API

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

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

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

  • 對(duì)象序列化包括如下步驟:
      1) 創(chuàng)建一個(gè)對(duì)象輸出流,它可以包裝一個(gè)其他類型的目標(biāo)輸出流,如文件輸出流;
      2) 通過對(duì)象輸出流的writeObject()方法寫對(duì)象。

  • 對(duì)象反序列化的步驟如下:
      1) 創(chuàng)建一個(gè)對(duì)象輸入流,它可以包裝一個(gè)其他類型的源輸入流,如文件輸入流;
      2) 通過對(duì)象輸入流的readObject()方法讀取對(duì)象。

serialVersionUID的作用

  • 字?面?意?思?上?是?序?列?化?的?版?本?號(hào)?,凡是實(shí)現(xiàn)Serializable接口的類都有一個(gè)表示序列化版本標(biāo)識(shí)符的靜態(tài)變量

  • 為什么要使用:

    當(dāng)我們要在序列化的類中添加一個(gè)屬性時(shí),假如一開始此類中沒有顯示的聲明版本號(hào)ID,那么在執(zhí)行反序列化操作時(shí)就會(huì)報(bào)異常,原因就是修改過后的class和以前的類不兼容了,出于安全機(jī)制考慮,程序拋出了異常,沒有反序列化成功。其中主要原因就是一開始沒有指定類的serialVersionUID,那么java編譯器會(huì)自動(dòng)給這個(gè)class進(jìn)行一個(gè)摘要算法,類似于指紋算法,只要這個(gè)文件 多一個(gè)空格,得到的UID就會(huì)截然不同的,可以保證在這么多類中,這個(gè)編號(hào)是唯一的。所以,添加了一個(gè)字段后,由于沒有顯指定 serialVersionUID,編譯器又為我們生成了一個(gè)UID,當(dāng)然和前面保存在文件中的那個(gè)不會(huì)一樣了,于是就出現(xiàn)了2個(gè)序列化版本號(hào)不一致的錯(cuò)誤。因此,只要我們自己指定了serialVersionUID,就可以在序列化后,去添加一個(gè)字段,或者方法,而不會(huì)影響到后期的還原,還原后的對(duì)象照樣可以使用,而且還多了方法或者屬性可以用。

  • 顯式地定義serialVersionUID有兩種用途:
       1、 在某些場合,希望類的不同版本對(duì)序列化兼容,因此需要確保類的不同版本具有相同的serialVersionUID;
       2、 在某些場合,不希望類的不同版本對(duì)序列化兼容,因此需要確保類的不同版本具有不同的serialVersionUID。

要傳遞的類實(shí)現(xiàn)Parcelable接口傳遞對(duì)象(android專用)

  • 概念
    進(jìn)行Android開發(fā)的時(shí)候,無法將對(duì)象的引用傳給Activities或者Fragments,我們需要將這些對(duì)象放到一個(gè)Intent或者Bundle里面,然后再傳遞
    不過不同于將對(duì)象進(jìn)行序列化,Parcelable方式的實(shí)現(xiàn)原理是將一個(gè)完整的對(duì)象進(jìn)行分解,而分解后的每一部分都是Intent所支持的數(shù)據(jù)類型,這樣也就實(shí)現(xiàn)傳遞對(duì)象的功能了。
  • Parcelable作用:
    1)永久性保存對(duì)象,保存對(duì)象的字節(jié)序列到本地文件中;
    2)通過序列化對(duì)象在網(wǎng)絡(luò)中傳遞對(duì)象;
    3)通過序列化在進(jìn)程間傳遞對(duì)象。
  • 實(shí)現(xiàn)
    • 1 describeContents方法。內(nèi)容接口描述,默認(rèn)返回0就可以

    • 2 writeToParcel 方法。打包數(shù)據(jù)到Parcel容器保存

    • 3 靜態(tài)的Parcelable.Creator接口,本接口有兩個(gè)方法:

    createFromParcel(Parcel in)  
    //從Parcel容器中讀取傳遞數(shù)據(jù)值,封裝成Parcelable對(duì)象返回邏輯    層。
    
    newArray(int size) 
    //創(chuàng)建一個(gè)類型為T,長度為size的數(shù)組,僅一句話(return new T[size])即可。方法是供外部類反序列化本類數(shù)組使用。
    
  • Serializable 和Parcelable的對(duì)比
    android上應(yīng)該盡量采用Parcelable,效率至上
    (1)編碼上:
    Serializable代碼量少,寫起來方便;Parcelable代碼多一些
    (2)效率上:
    Parcelable的速度比 Serializable高十倍以上;serializable的迷人之處在于你只需要對(duì)某個(gè)類實(shí)現(xiàn)Serializable 接口即可。Serializable 接口是一種標(biāo)識(shí)接口(marker interface),這意味著無需實(shí)現(xiàn)方法,Java便會(huì)對(duì)這個(gè)對(duì)象進(jìn)行高效的序列化操作。
    這種方法的缺點(diǎn)是使用了反射,序列化的過程較慢。這種機(jī)制會(huì)在序列化的時(shí)候創(chuàng)建許多的臨時(shí)對(duì)象,容易觸發(fā)垃圾回收。
    Parcelable方式的實(shí)現(xiàn)原理是將一個(gè)完整的對(duì)象進(jìn)行分解,而分解后的每一部分都是Intent所支持的數(shù)據(jù)類型,這樣也就實(shí)現(xiàn)傳遞對(duì)象的功能了
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容