序列化和反序列化

序列化簡(jiǎn)介

Java序列化,一個(gè)日常開發(fā)中比較少用到的技術(shù)。正常情況下,JVM啟動(dòng)后,我們可以創(chuàng)建對(duì)象,JVM關(guān)閉后,我們創(chuàng)建過的對(duì)象都隨之銷毀,資源釋放。但有些時(shí)候可能要求在JVM停止之后,某些對(duì)象需要保存起來,以便將來再重新讀取它們。

舉個(gè)例子,應(yīng)用服務(wù)器的HttpSession對(duì)象,Session是指瀏覽器與服務(wù)器之間的一次會(huì)話,對(duì)應(yīng)的是服務(wù)器中的一個(gè)Session對(duì)象,而客戶端中保存一個(gè)jsessionid。那么當(dāng)某種情況下,我們不得不重啟服務(wù)器的時(shí)候,就需要把之前所有的Session對(duì)象保存起來,服務(wù)器重啟之后,將這些Session對(duì)象再重新加載過來,這樣避免了之前瀏覽器與服務(wù)器建立的會(huì)話失效,在瀏覽器那看來,就好象服務(wù)器沒有關(guān)閉過一樣(假設(shè)服務(wù)器重啟期間用戶沒有操作)。這就用到了Java序列化技術(shù),關(guān)于這個(gè)例子,我們可以拿Tomcat來測(cè)試一下,注意要用正常的手段來關(guān)閉服務(wù)器(shutdown.bat),而非強(qiáng)制關(guān)閉,強(qiáng)制關(guān)閉沒有序列化的過程。

Java序列化

首先創(chuàng)建一個(gè)可序列化的JavaBean類:Name.java

import java.io.Serializable;  
  
/** 
 * 可序列化的類,需要實(shí)現(xiàn)Serializable接口 
 */  
public class Name implements Serializable {  
  
    private String firstname;  
      
    private String lastname;  
      
    public Name() {  
        System.out.println("無參構(gòu)造器");  
    }  
  
    public Name(String firstname, String lastname) {  
        System.out.println("全參構(gòu)造器");  
        this.firstname = firstname;  
        this.lastname = lastname;  
    }  
  
    //省略getter和setter

    @Override  
    public String toString() {  
        return "我的名字是" + firstname + "," + lastname;  
    }  
      
}  

再實(shí)現(xiàn)一個(gè)序列化的工具類:Serializations.java

import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
  
/** 
 * 序列化工具類 
 */  
public class Serializations {  
  
    /** 
     * 序列化對(duì)象到指定路徑文件 
     * @param outPath 文件路徑 
     * @param outObj 需要序列化的對(duì)象 
     * @throws IOException 當(dāng)I/O發(fā)生異常時(shí) 
     */  
    public static void serialize(String outPath, Object outObj) 
        throws IOException {  
        ObjectOutputStream oos = null;  
        try {  
            oos = new ObjectOutputStream(new FileOutputStream(outPath));  
            oos.writeObject(outObj);  
        } finally {  
            if(oos != null)    oos.close();  
        }  
    }  
      
    /** 
     * 從文件中逆序列化出對(duì)象 
     * @param inPath 文件路徑 
     * @return 你序列化出的對(duì)象 
     * @throws IOException 當(dāng)I/O發(fā)生異常時(shí) 
     * @throws ClassNotFoundException 當(dāng)文件中不存在序列化的對(duì)象時(shí) 
     */  
    public static Object deserialize(String inPath) 
        throws IOException, ClassNotFoundException {  
        ObjectInputStream ois = null;  
        try {  
            ois = new ObjectInputStream(new FileInputStream(inPath));  
            return ois.readObject();  
        } finally {  
            if(ois != null)    ois.close();  
        }  
    }  
}  

最后創(chuàng)建兩個(gè)個(gè)測(cè)試類,來使用一下序列化方法和逆序列化方法:WriteObject.java。

import java.io.IOException;  
  
public class WriteObject {  
  
    public static void main(String[] args) throws IOException {  
        Name name = new Name("科比", "布萊恩特");  
        Serializations.serialize(args[0], name);  
    }  
}  

運(yùn)行后,指定目錄下會(huì)生成相應(yīng)文件,其內(nèi)包含了name對(duì)象信息。

ReadObject.java:

import java.io.IOException;  
  
public class ReadObject {  
  
    public static void main(String[] args) 
        throws ClassNotFoundException, IOException {  
        Object obj = Serializations.deserialize(args[0]);  
        System.out.println(obj);  
    }  

運(yùn)行后,輸出:我的名字是科比,布萊恩特

我們成功的將name對(duì)象序列化到了指定文件中,并且通過逆序列化得到一個(gè)和原對(duì)象屬性相同的對(duì)象。注意,逆序列化出的對(duì)象沒有使用該對(duì)象的構(gòu)造器(由輸出結(jié)果可以證明),并且和原對(duì)象不相等。

對(duì)象的默認(rèn)序列化機(jī)制
序列化時(shí),對(duì)象的類、類的簽名,以及類及其所有超類型的非瞬態(tài)(non-transient)和非靜態(tài)(non-static)字段的值都將被寫入。逆序列化時(shí),對(duì)象的類、類的簽名,以及類及其所有超類型的非瞬態(tài)(non-transient)和非靜態(tài)(non-static)字段的值都將被讀取。如果我們想某個(gè)成員變量不被序列化,可以在其前面加入transient關(guān)鍵字。如:

private transient String lastname;  

序列化版本號(hào)

如果對(duì)象所屬類在對(duì)象序列化之后做了修改,比如修改屬性名稱、類型、修飾符等等,再次逆序列化就會(huì)發(fā)生異常,如我們將lastname前加入transient,使用ReadObject.java進(jìn)行逆序列化, 將會(huì)拋出如下異常:

Exception in thread "main" java.io.InvalidClassException: Name; local class incompatible: stream classdesc serialVersionUID = 3999552307707967101, local class serialVersionUID = -4860856635192050881  
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604)  
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601)  
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)  
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)  
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)  
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)  
    at Serializations.deserialize(Serializations.java:43)  
    at ReadObject.main(SerializationsTest.java:14)  

異常的大概描述是說流中的類的版本號(hào)和本地類的版本號(hào)不一致,這里要引入一個(gè)序列化版本號(hào)(serialVersionUID)的概念,serialVersionUID是一個(gè)64位的值,在類中需要聲明為private static final long,它可以人為來維護(hù),也可以通過JVM實(shí)現(xiàn)的算法來生成,安裝JDK后,可以過%JAVA_HOME%/bin/serialver.exe來生成serialVersionUID。在逆序列化時(shí),會(huì)將從對(duì)象流中讀取的類信息和當(dāng)前classpath下的相應(yīng)類的類信息(Name.class)進(jìn)行比對(duì),比對(duì)的媒介就是serialVersionUID,如果對(duì)象中沒有聲明serialVersionUID,那么該值就會(huì)通過默認(rèn)的算法生成,兩端不一致時(shí),就會(huì)拋出上面的異常,逆序列化失敗。

當(dāng)編寫一個(gè)可序列化的類時(shí)(Name.java),可以給serialVersionUID賦一個(gè)即簡(jiǎn)單又易理解的值,如:

private static final long serialVersionUID = 1L;  

如果對(duì)該類進(jìn)行了更改,可能需要同時(shí)更新serialVersionUID,如:

private static final long serialVersionUID = 2L;  

但有時(shí)我們可能即使更改了類之后,仍然要保持之前序列化的可逆性,也就是對(duì)之前的序列化文件做個(gè)兼容,那么就不能更新serialVersionUID的值,這時(shí)更改前生成的序列化文件依然可逆序列化,那么其更新的字段會(huì)以字段類型的預(yù)設(shè)值逆序列化,避開不兼容的問題。

復(fù)合類序列化

上文中實(shí)現(xiàn)了序列化和逆序列化一個(gè)簡(jiǎn)單的Name對(duì)象,下面來看一個(gè)稍復(fù)雜的情況,Name類中復(fù)合了其它類。

import java.io.Serializable;  
  
/** 
 * 可序列化的類,需要實(shí)現(xiàn)Serializable接口 
 */  
public class Name implements Serializable {  
  
    private static final long serialVersionUID = 1L;  
  
    private String firstname;  
      
    private String lastname;  
      
    private Nickname nickname;  
      
    public Name() {}  
  
    public Name(String firstname, String lastname) {  
        this.firstname = firstname;  
        this.lastname = lastname;  
    }  
      
    public Name(String firstname, String lastname, Nickname nickname) {  
        this.firstname = firstname;  
        this.lastname = lastname;  
        this.nickname = nickname;  
    }  
  
    public String getFirstname() {  
        return firstname;  
    }  
  
    public void setFirstname(String firstname) {  
        this.firstname = firstname;  
    }  
  
    public String getLastname() {  
        return lastname;  
    }  
  
    public void setLastname(String lastname) {  
        this.lastname = lastname;  
    }  
      
    public Nickname getNickname() {  
        return nickname;  
    }  
  
    public void setNickname(Nickname nickname) {  
        this.nickname = nickname;  
    }  
  
    @Override  
    public String toString() {  
        return "我的名字是" + firstname + "," + lastname + "\n我的昵稱是" + nickname;  
    }  
      
}  

Nickname.java:

import java.io.Serializable;  
  
/** 
 * 昵稱類 
 */  
public class Nickname implements Serializable {  
  
    private static final long serialVersionUID = 1L;  
      
    private String name;  
      
    public Nickname() {}  
  
    //省略getter和setter
  
    @Override  
    public String toString() {  
        return name;  
    }  
      
}  

WriteObject.java:

import java.io.IOException;  
  
import com.runqianapp.test.bean.Name;  
import com.runqianapp.test.bean.Nickname;  
  
public class WriteObject {  
  
    public static void main(String[] args) throws IOException {  
        Nickname nickname = new Nickname("黑曼巴");  
        Name name = new Name("科比", "布萊恩特", nickname);  
        Serializations.serialize(args[0], name);  
    }  
}  

運(yùn)行后,指定目錄下會(huì)生成相應(yīng)文件,再次運(yùn)行ReadObject.java,會(huì)得到如下輸出信息:

我的名字是科比,布萊恩特  
我的昵稱是黑曼巴  

在序列化對(duì)象時(shí),不僅會(huì)序列化當(dāng)前對(duì)象本身,還會(huì)對(duì)該對(duì)象引用的其它對(duì)象也進(jìn)行序列化,同樣地,這些其它對(duì)象引用的另外對(duì)象也將被序列化,以此類推。在序列化過程中,可能會(huì)遇到不支持可序列化接口的對(duì)象,在此情況下,將拋出 NotSerializableException,并將標(biāo)識(shí)不可序列化對(duì)象的類。如將Nickname.java去掉Serializable接口,再次運(yùn)行WriteObject.java,會(huì)拋出如下異常:

Exception in thread "main" java.io.NotSerializableException: Nickname  
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)  
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1528)  
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1493)  
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1416)  
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)  
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)  
    at Serializations.serialize(Serializations.java:26)  
    at WriteObject.main(WriteObject.java:13)  

我們可以用transient來修飾nickname屬性,這樣該類就可以正常序列化了,但是nickname中的屬性也就無法序列化了,那我們?nèi)绾巫尣荒苄蛄谢念怤ickName中的name屬性可以序列化和反序列化呢?在序列化和反序列化過程中需要特殊處理的類必須使用下列準(zhǔn)確簽名來實(shí)現(xiàn)特殊方法:

public class Name implements Serializable {  
    ...  
    transient private Nickname nickname; 
    ...   
  
    private void writeObject(ObjectOutputStream out) throws IOException {  
        // 默認(rèn)序列化機(jī)制  
        out.defaultWriteObject();  
        // 序列化nickname中的name屬性  
        out.writeObject(nickname.getName());  
    }  
  
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
        // 默認(rèn)逆序列化機(jī)制  
        in.defaultReadObject();  
        // 逆序列化一個(gè)nickname對(duì)象  
       nickname = new Nickname(in.readObject().toString()); 
    }  
}  

這樣就可以處理其不可序列化的復(fù)合類Nickname中的name屬性序列化及反序列化。運(yùn)行WriteObject和ReadObject,序列化和反序列化成功。這兩個(gè)方法如何實(shí)現(xiàn)取決于最終的需求。

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 11,163評(píng)論 0 24
  • 1. Java序列化和反序列化(What) Java序列化(Serialize)是指將一個(gè)Java對(duì)象寫入IO流中...
    悠揚(yáng)前奏閱讀 971評(píng)論 2 1
  • 1.背景 某天,我在寫代碼定義 bean 的時(shí)候,順手寫了個(gè) public class User implemen...
    李眼鏡閱讀 854評(píng)論 0 2
  • 什么是序列化? 序列化是將對(duì)象存儲(chǔ)為二進(jìn)制格式。在序列化的過程中,對(duì)象和它的元數(shù)據(jù)(比如對(duì)象的類名和它的屬性名稱)...
    Chokez閱讀 1,142評(píng)論 0 0
  • 先問幾個(gè)問題,大家覺得寫文檔是一件必要的事嗎?你喜歡寫文檔嗎??你寫的文檔程序猿和測(cè)試會(huì)看嗎?? 假如你自己能獨(dú)立...
    烏木閱讀 16,474評(píng)論 1 42

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