Java對(duì)象的序列化與反序列化

序列化與反序列化

序列化 (Serialization)是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^(guò)程瘫辩。一般將一個(gè)對(duì)象存儲(chǔ)至一個(gè)儲(chǔ)存媒介仲闽,例如檔案或是記億體緩沖等棋傍。在網(wǎng)絡(luò)傳輸過(guò)程中倔毙,可以是字節(jié)或是XML等格式冻璃。而字節(jié)的或XML編碼格式可以還原完全相等的對(duì)象侮穿。這個(gè)相反的過(guò)程又稱為反序列化歌径。

Java對(duì)象的序列化與反序列化

在Java中,我們可以通過(guò)多種方式來(lái)創(chuàng)建對(duì)象亲茅,并且只要對(duì)象沒(méi)有被回收我們都可以復(fù)用該對(duì)象回铛。但是,我們創(chuàng)建出來(lái)的這些Java對(duì)象都是存在于JVM的堆內(nèi)存中的克锣。只有JVM處于運(yùn)行狀態(tài)的時(shí)候茵肃,這些對(duì)象才可能存在。一旦JVM停止運(yùn)行袭祟,這些對(duì)象的狀態(tài)也就隨之而丟失了验残。
但是在真實(shí)的應(yīng)用場(chǎng)景中,我們需要將這些對(duì)象持久化下來(lái)巾乳,并且能夠在需要的時(shí)候把對(duì)象重新讀取出來(lái)您没。Java的對(duì)象序列化可以幫助我們實(shí)現(xiàn)該功能。
對(duì)象序列化機(jī)制(object serialization)是Java語(yǔ)言內(nèi)建的一種對(duì)象持久化方式胆绊,通過(guò)對(duì)象序列化氨鹏,可以把對(duì)象的狀態(tài)保存為字節(jié)數(shù)組,并且可以在有需要的時(shí)候?qū)⑦@個(gè)字節(jié)數(shù)組通過(guò)反序列化的方式再轉(zhuǎn)換成對(duì)象压状。對(duì)象序列化可以很容易的在JVM中的活動(dòng)對(duì)象和字節(jié)數(shù)組(流)之間進(jìn)行轉(zhuǎn)換喻犁。
在Java中,對(duì)象的序列化與反序列化被廣泛應(yīng)用到RMI(遠(yuǎn)程方法調(diào)用)及網(wǎng)絡(luò)傳輸中何缓。

相關(guān)接口及類

Java為了方便開(kāi)發(fā)人員將Java對(duì)象進(jìn)行序列化及反序列化提供了一套方便的API來(lái)支持。其中包括以下接口和類:

java.io.Serializable
java.io.Externalizable
ObjectOutput
ObjectInput
ObjectOutputStream
ObjectInputStream

Serializable 接口

類通過(guò)實(shí)現(xiàn) java.io.Serializable 接口以啟用其序列化功能还栓。未實(shí)現(xiàn)此接口的類將無(wú)法使其任何狀態(tài)序列化或反序列化碌廓。可序列化類的所有子類型本身都是可序列化的剩盒。序列化接口沒(méi)有方法或字段谷婆,僅用于標(biāo)識(shí)可序列化的語(yǔ)義。
當(dāng)試圖對(duì)一個(gè)對(duì)象進(jìn)行序列化的時(shí)候辽聊,如果遇到不支持 Serializable 接口的對(duì)象纪挎。在此情況下,將拋出 NotSerializableException跟匆。
如果要序列化的類有父類异袄,要想同時(shí)將在父類中定義過(guò)的變量持久化下來(lái),那么父類也應(yīng)該集成java.io.Serializable接口玛臂。
下面是一個(gè)實(shí)現(xiàn)了java.io.Serializable接口的類

import java.io.Serializable;

public class User1 implements Serializable {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

通過(guò)下面的代碼進(jìn)行序列化及反序列化

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;
/**
 * 
 * SerializableDemo1 結(jié)合SerializableDemo2說(shuō)明 一個(gè)類要想被序列化必須實(shí)現(xiàn)Serializable接口
 */
public class SerializableDemo1 {

    public static void main(String[] args) {
        //Initializes The Object
        User1 user = new User1();
        user.setName("hollis");
        user.setAge(23);
        System.out.println(user);

        //Write Obj to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }

        //Read Obj from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User1 newUser = (User1) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

//OutPut:
//User{name='hollis', age=23}
//User{name='hollis', age=23}

Externalizable接口

除了Serializable 之外烤蜕,java中還提供了另一個(gè)序列化接口Externalizable封孙,為了了解Externalizable接口和Serializable接口的區(qū)別,先來(lái)看代碼讽营,我們把上面的代碼改成使用Externalizable的形式虎忌。

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

/**
 * 
 * 實(shí)現(xiàn)Externalizable接口
 */
public class User1 implements Externalizable {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void writeExternal(ObjectOutput out) throws IOException {

    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
import java.io.*;

public class ExternalizableDemo1 {

    //為了便于理解和節(jié)省篇幅,忽略關(guān)閉流操作及刪除文件操作橱鹏。真正編碼時(shí)千萬(wàn)不要忘記
    //IOException直接拋出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        User1 user = new User1();
        user.setName("hollis");
        user.setAge(23);
        oos.writeObject(user);
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        User1 newInstance = (User1) ois.readObject();
        //output
        System.out.println(newInstance);
    }
}
//OutPut:
//User{name='null', age=0}

通過(guò)上面的實(shí)例可以發(fā)現(xiàn)膜蠢,對(duì)User1類進(jìn)行序列化及反序列化之后得到的對(duì)象的所有屬性的值都變成了默認(rèn)值。也就是說(shuō)莉兰,之前的那個(gè)對(duì)象的狀態(tài)并沒(méi)有被持久化下來(lái)挑围。這就是Externalizable接口和Serializable接口的區(qū)別:Externalizable繼承了Serializable,該接口中定義了兩個(gè)抽象方法:
writeExternal()與readExternal()贮勃。當(dāng)使用Externalizable接口來(lái)進(jìn)行序列化與反序列化的時(shí)候需要開(kāi)發(fā)人員重寫(xiě)writeExternal()與readExternal()方法贪惹。由于上面的代碼中,并沒(méi)有在這兩個(gè)方法中定義序列化實(shí)現(xiàn)細(xì)節(jié)寂嘉,所以輸出的內(nèi)容為空奏瞬。還有一點(diǎn)值得注意:在使用Externalizable進(jìn)行序列化的時(shí)候,在讀取對(duì)象時(shí)泉孩,會(huì)調(diào)用被序列化類的無(wú)參構(gòu)造器去創(chuàng)建一個(gè)新的對(duì)象硼端,然后再將被保存對(duì)象的字段的值分別填充到新對(duì)象中。所以寓搬,實(shí)現(xiàn)Externalizable接口的類必須要提供一個(gè)public的無(wú)參的構(gòu)造器珍昨。
按照要求修改之后代碼如下:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

/**
 * 
 * 實(shí)現(xiàn)Externalizable接口,并實(shí)現(xiàn)writeExternal和readExternal方法
 */
public class User2 implements Externalizable {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
import java.io.*;

public class ExternalizableDemo2 {

    //為了便于理解和節(jié)省篇幅,忽略關(guān)閉流操作及刪除文件操作句喷。真正編碼時(shí)千萬(wàn)不要忘記
    //IOException直接拋出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        User2 user = new User2();
        user.setName("hollis");
        user.setAge(23);
        oos.writeObject(user);
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        User2 newInstance = (User2) ois.readObject();
        //output
        System.out.println(newInstance);
    }
}
//OutPut:
//User{name='hollis', age=23}

這次镣典,就可以把之前的對(duì)象狀態(tài)持久化下來(lái)了。

如果User類中沒(méi)有無(wú)參數(shù)的構(gòu)造函數(shù)唾琼,在運(yùn)行時(shí)會(huì)拋出異常:java.io.InvalidClassException

ObjectOutput 和 ObjectInput 接口

ObjectInput接口 擴(kuò)展自 DataInput 接口以包含對(duì)象的讀操作兄春。

DataInput 接口用于從二進(jìn)制流中讀取字節(jié),并根據(jù)所有 Java 基本類型數(shù)據(jù)進(jìn)行重構(gòu)锡溯。同時(shí)還提供根據(jù) UTF-8 修改版格式的數(shù)據(jù)重構(gòu) String 的工具赶舆。
對(duì)于此接口中的所有數(shù)據(jù)讀取例程來(lái)說(shuō),如果在讀取所需字節(jié)數(shù)之前已經(jīng)到達(dá)文件末尾 (end of file)祭饭,則將拋出 EOFException(IOException 的一種)芜茵。如果因?yàn)榈竭_(dá)文件末尾以外的其他原因無(wú)法讀取字節(jié),則將拋出 IOException 而不是 EOFException倡蝙。尤其是九串,在輸入流已關(guān)閉的情況下,將拋出 IOException寺鸥。

ObjectOutput 擴(kuò)展 DataOutput 接口以包含對(duì)象的寫(xiě)入操作蒸辆。

DataOutput 接口用于將數(shù)據(jù)從任意 Java 基本類型轉(zhuǎn)換為一系列字節(jié)征炼,并將這些字節(jié)寫(xiě)入二進(jìn)制流。同時(shí)還提供了一個(gè)將 String 轉(zhuǎn)換成 UTF-8 修改版格式并寫(xiě)入所得到的系列字節(jié)的工具躬贡。
對(duì)于此接口中寫(xiě)入字節(jié)的所有方法谆奥,如果由于某種原因無(wú)法寫(xiě)入某個(gè)字節(jié),則拋出 IOException拂玻。

ObjectOutputStream 類和 ObjectInputStream 類

通過(guò)前面的代碼片段中我們也能知道酸些,我們一般使用ObjectOutputStream的writeObject方法把一個(gè)對(duì)象進(jìn)行持久化。再使用ObjectInputStream的readObject從持久化存儲(chǔ)中把對(duì)象讀取出來(lái)檐蚜。
更多關(guān)于ObjectInputStream和ObjectOutputStream的相關(guān)知識(shí)請(qǐng)閱讀另外兩篇博文:深入分析Java的序列化與反序列化魄懂、單例與序列化的那些事兒

序列化ID

虛擬機(jī)是否允許反序列化啼染,不僅取決于類路徑和功能代碼是否一致韧骗,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L织阳,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成)咳短,在這里有一個(gè)建議填帽,如果沒(méi)有特殊需求,就是用默認(rèn)的 1L 就可以咙好,這樣可以確保代碼一致時(shí)反序列化成功篡腌。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候勾效,通過(guò)改變序列化 ID 可以用來(lái)限制某些用戶的使用嘹悼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市层宫,隨后出現(xiàn)的幾起案子杨伙,更是在濱河造成了極大的恐慌,老刑警劉巖萌腿,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缀台,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哮奇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)睛约,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鼎俘,“玉大人,你說(shuō)我怎么就攤上這事辩涝∶撤ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵怔揩,是天一觀的道長(zhǎng)捉邢。 經(jīng)常有香客問(wèn)我脯丝,道長(zhǎng),這世上最難降的妖魔是什么伏伐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任宠进,我火速辦了婚禮,結(jié)果婚禮上藐翎,老公的妹妹穿的比我還像新娘材蹬。我一直安慰自己,他們只是感情好吝镣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布堤器。 她就那樣靜靜地躺著,像睡著了一般末贾。 火紅的嫁衣襯著肌膚如雪闸溃。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天拱撵,我揣著相機(jī)與錄音辉川,去河邊找鬼。 笑死裕膀,一個(gè)胖子當(dāng)著我的面吹牛员串,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昼扛,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼寸齐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了抄谐?” 一聲冷哼從身側(cè)響起渺鹦,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛹含,沒(méi)想到半個(gè)月后毅厚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浦箱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年吸耿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酷窥。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咽安,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蓬推,到底是詐尸還是另有隱情妆棒,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站糕珊,受9級(jí)特大地震影響动分,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜红选,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一澜公、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纠脾,春花似錦玛瘸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至慧脱,卻和暖如春渺绒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菱鸥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工宗兼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人氮采。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓殷绍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鹊漠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子主到,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 先看看概念介紹: 序列化:把對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程稱為對(duì)象的序列化。 反序列化:把字節(jié)序列恢復(fù)為對(duì)象的過(guò)程稱為對(duì)...
    漫不經(jīng)心v閱讀 454評(píng)論 0 1
  • “最好的教材就是源碼注釋躯概,然后是大牛的總結(jié)登钥。” 從今天開(kāi)始寫(xiě)博客娶靡,目的很明確牧牢,梳理零碎的java知識(shí),總結(jié)并記錄下...
    蝸牛在北京閱讀 849評(píng)論 1 1
  • 在Java中姿锭,我們可以通過(guò)多種方式來(lái)創(chuàng)建對(duì)象塔鳍,并且只要對(duì)象沒(méi)有被回收我們都可以復(fù)用該對(duì)象。但是呻此,我們創(chuàng)建出來(lái)的這些...
    懶癌正患者閱讀 1,526評(píng)論 0 12
  • 一轮纫、序列化和反序列化的概念 把對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程稱為對(duì)象的序列化。 把字節(jié)序列恢復(fù)為對(duì)象的過(guò)程稱為對(duì)象的反序...
    燁楓_邱閱讀 35,872評(píng)論 0 11
  • 一趾诗、序列化和反序列化的概念 把對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程稱為對(duì)象的序列化。把字節(jié)序列恢復(fù)為對(duì)象的過(guò)程稱為對(duì)象的反序列...
    叨唧唧的閱讀 717評(píng)論 0 0