Java 對象序列化和反序列化

?????之前的文章中我們介紹過有關(guān)字節(jié)流字符流的使用在刺,當(dāng)時(shí)我們對于將一個(gè)對象輸出到流中的操作逆害,使用DataOutputStream流將該對象中的每個(gè)屬性值逐個(gè)輸出到流中,讀出時(shí)相反蚣驼。在我們看來這種行為實(shí)在是繁瑣魄幕,尤其是在這個(gè)對象中屬性值很多的時(shí)候∮毙樱基于此纯陨,Java中對象的序列化機(jī)制就可以很好的解決這種操作。本篇就簡單的介紹Java對象序列化留储,主要內(nèi)容如下:

  • 簡潔的代碼實(shí)現(xiàn)
  • 序列化實(shí)現(xiàn)的基本算法
  • 兩種特殊的情況
  • 自定義序列化機(jī)制
  • 序列化的版本控制

一翼抠、簡潔的代碼實(shí)現(xiàn)
?????在介紹對象序列化的使用方法之前,先看看我們之前是怎么存儲(chǔ)一個(gè)對象類型的數(shù)據(jù)的获讳。

//簡單定義一個(gè)Student類
public class Student {

    private String name;
    private int age;

    public Student(){}
    public Student(String name,int age){
        this.name = name;
        this.age=age;
    }

    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
    //重寫toString
    @Override
    public String toString(){
        return ("my name is:"+this.name+" age is:"+this.age);
    }
}
//main方法實(shí)現(xiàn)了將對象寫入文件并讀取出來
public static void main(String[] args) throws IOException{

        DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt"));
        Student stuW = new Student("walker",21);
        //將此對象寫入到文件中
        dot.writeUTF(stuW.getName());
        dot.writeInt(stuW.getAge());
        dot.close();

        //將對象從文件中讀出
        DataInputStream din = new DataInputStream(new FileInputStream("hello.txt"));
        Student stuR = new Student();
        stuR.setName(din.readUTF());
        stuR.setAge(din.readInt());
        din.close();

        System.out.println(stuR);
    }
輸出結(jié)果:my name is:walker age is:21

?????顯然這種代碼書寫是繁瑣的阴颖,接下來我們看看,如何使用序列化來完成保存對象的信息丐膝。

public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        Student stuW = new Student("walker",21);
        oos.writeObject(stuW);
        oos.close();

        //從文件中讀取該對象返回
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Student stuR = (Student)ois.readObject();
        System.out.println(stuR);
    }

?????寫入文件時(shí)量愧,只用了一條語句就是writeObject,讀取時(shí)也是只用了一條語句readObject帅矗。并且Student中的那些set偎肃,get方法都用不到了。是不是很簡潔呢损晤?接下來介紹實(shí)現(xiàn)細(xì)節(jié)软棺。

二、實(shí)現(xiàn)序列化的基本算法
?????在這種機(jī)制中尤勋,每個(gè)對象都是對應(yīng)著唯一的一個(gè)序列號(hào)喘落,而每個(gè)對象在被保存的時(shí)候也是根據(jù)這個(gè)序列號(hào)來對應(yīng)著每個(gè)不同的對象,對象序列化就是指利用了每個(gè)對象的序列號(hào)進(jìn)行保存和讀取的最冰。首先以寫對象到流中為例瘦棋,對于每個(gè)對象,第一次遇到的時(shí)候會(huì)將這個(gè)對象的基本信息保存到流中暖哨,如果當(dāng)前遇到的對象已經(jīng)被保存過了赌朋,就不會(huì)再次保存這些信息,轉(zhuǎn)而記錄此對象的序列號(hào)(因?yàn)閿?shù)據(jù)沒必要重復(fù)保存)。對于讀的情況沛慢,從流中遇到的每個(gè)對象赡若,如果第一次遇到,直接輸出团甲,如果讀取到的是某個(gè)對象的序列號(hào)逾冬,就會(huì)找到相關(guān)聯(lián)的對象,輸出躺苦。
?????說明幾點(diǎn)身腻,一個(gè)對象要想是可序列化的,就必須實(shí)現(xiàn)接口 java.io.Serializable;匹厘,這是一個(gè)標(biāo)記接口嘀趟,不用實(shí)現(xiàn)任何的方法。而我們的ObjectOutputStream流愈诚,就是一個(gè)可以將對象信息轉(zhuǎn)為字節(jié)的流她按,構(gòu)造函數(shù)如下:

public ObjectOutputStream(OutputStream out)

?????也就是所有字節(jié)流都可以作為參數(shù)傳入,兼容一切字節(jié)操作扰路。在這個(gè)流中定義了writeObject和readObject方法尤溜,實(shí)現(xiàn)了序列化對象和反序列化對象。當(dāng)然汗唱,我們也是可以通過在類中實(shí)現(xiàn)這兩個(gè)方法來自定義序列化機(jī)制,具體的后文介紹丈攒。此處我們只需要了解整個(gè)序列化機(jī)制哩罪,所有的對象數(shù)據(jù)只會(huì)保存一份,至于相同的對象再次出現(xiàn)巡验,只保存對應(yīng)的序列號(hào)际插。下面,通過兩個(gè)特殊的情況直觀的感受下他的這個(gè)基本算法显设。

三框弛、兩個(gè)特殊的實(shí)例
?????先看第一個(gè)實(shí)例:

public class Student implements Serializable {

    String name;
    int age;
    Teacher t;  //另外一個(gè)對象類型

    public Student(){}
    public Student(String name,int age,Teacher t){
        this.name = name;
        this.age=age;
        this.t = t;
    }

    public void setName(String name){this.name = name;}
    public void setAge(int age){this.age = age;}
    public void setT(Teacher t){this.t = t;}
    public String getName(){return this.name;}
    public int getAge(){return this.age;}
    public Teacher getT(){return this.t;}
}

public class Teacher implements Serializable {
    String name;

    public Teacher(String name){
        this.name = name;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

        Teacher t = new Teacher("li");
        Student stu1 = new Student("walker",21,t);
        Student stu2 = new Student("yam",22,t);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        oos.writeObject(stu1);
        oos.writeObject(stu2);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Student stuR1 = (Student)ois.readObject();
        Student stuR2 = (Student)ois.readObject();

        if (stuR1.getT() == stuR2.getT())
            System.out.println("相同對象");
    }

?????結(jié)果是很顯而易見的,輸出了相同對象捕捂。我們在main函數(shù)中定義了兩個(gè)student類型對象瑟枫,他們卻都引用的同一個(gè)teacher對象在內(nèi)部。完成序列化之后指攒,反序列化出來兩個(gè)對象慷妙,通過比較他們內(nèi)部的teacher對象是否是同一個(gè)實(shí)例,可以看出來允悦,在序列化第一個(gè)student對象的時(shí)候t是被寫入流中的膝擂,但是在遇到第二個(gè)student對象的teacher對象實(shí)例時(shí),發(fā)現(xiàn)前面已經(jīng)寫過了,于是不再寫入流中架馋,只保存對應(yīng)的序列號(hào)作為引用弧蝇。當(dāng)然在反序列化的時(shí)候,原理類似蔼水。這和我們上面介紹的基本算法是一樣的绿聘。
?????下面看第二個(gè)特殊實(shí)例:

public class Student implements Serializable {

    String name;
    Teacher t;

}

public class Teacher implements Serializable {
    String name;
    Student stu;

}

public static void main(String[] args) throws IOException, ClassNotFoundException {

        Teacher t = new Teacher();
        Student s =new Student();
        t.name = "walker";
        t.stu = s;
        s.name = "yam";
        s.t = t;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        oos.writeObject(t);
        oos.writeObject(s);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Teacher tR = (Teacher)ois.readObject();
        Student sR = (Student)ois.readObject();
        if(tR == sR.t && sR == tR.stu)System.out.println("ok");

    }

?????輸出的結(jié)果是ok,這個(gè)例子可以叫做:循環(huán)引用办绝。從結(jié)果我們可以看出來伊约,序列化之前兩個(gè)對象存在的相互的引用關(guān)系,經(jīng)過序列化之后孕蝉,兩者之間的這種引用關(guān)系是依然存在的屡律。其實(shí)按照我們之前介紹的判斷算法來看,首先我們先序列化了teacher對象降淮,因?yàn)樗麅?nèi)部引用了student的對象超埋,兩者都是第一次遇到,所以將兩者序列化到流中佳鳖,然后我們?nèi)バ蛄谢痵tudent對象霍殴,發(fā)現(xiàn)這個(gè)對象以及內(nèi)部的teacher對象都已經(jīng)被序列化了,于是只保存對應(yīng)的序列號(hào)系吩。讀取的時(shí)候根據(jù)序列號(hào)恢復(fù)對象来庭。

四、自定義序列化機(jī)制
?????綜上穿挨,我們已經(jīng)介紹完了基本的序列化與反序列化的知識(shí)月弛。但是往往我們會(huì)有一些特殊的要求,這種默認(rèn)的序列化機(jī)制雖然已經(jīng)很完善了科盛,但是有些時(shí)候還是不能滿足我們的需求帽衙。所以我們看看如何自定義序列化機(jī)制。自定義序列化機(jī)制中贞绵,我們會(huì)使用到一個(gè)關(guān)鍵字厉萝,它也是我們之前在看源碼的時(shí)候經(jīng)常遇到的,transient榨崩。將字段聲明transient谴垫,等于是告訴默認(rèn)的序列化機(jī)制,這個(gè)字段你不要給我寫到流中去蜡饵,我會(huì)自己處理的弹渔。、

public class Student implements Serializable {

    String name;
    transient int age;

    public String toString(){
        return this.name + ":" + this.age;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
        Student stu = new Student();
        stu.name = "walker";stu.age = 21;
        oos.writeObject(stu);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
        Student stuR = (Student)ois.readObject();

        System.out.println(stuR);
    }
輸出結(jié)果:walker:0

?????我們不是給age字段賦初始值了么溯祸,怎么會(huì)是0呢肢专?正如我們上文所說的一樣舞肆,被transient修飾的字段不會(huì)被寫入流中,自然讀取出來就沒有值博杖,默認(rèn)是0椿胯。下面看看我們怎么自己來序列化這個(gè)age。

//改動(dòng)過的student類剃根,main方法沒有改動(dòng)哩盲,大家可以往上看
public class Student implements Serializable {

    String name;
    transient int age;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();

        oos.writeInt(25);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();

        age = ois.readInt();
    }

    public String toString(){
        return this.name + ":" + this.age;
    }
}
輸出結(jié)果:walker:25

?????結(jié)果既不是我么初始化的21,也不是0狈醉,而是我們在writeObject方法中寫的25×停現(xiàn)在我們一點(diǎn)一點(diǎn)看看每個(gè)步驟的意義。首先苗傅,要想要實(shí)現(xiàn)自定義序列化抒线,就需要在該對象定義的類中實(shí)現(xiàn)兩個(gè)方法,writeObject和readObject渣慕,而且格式必須和上面貼出來的一樣嘶炭,筆者試過改動(dòng)方法修飾符,結(jié)果導(dǎo)致不能成功序列化逊桦。這是因?yàn)檎A裕琂ava采用反射機(jī)制,檢查該對象所在的類中有沒有實(shí)現(xiàn)這兩個(gè)方法强经,沒有的話就使用默認(rèn)的ObjectOutputStream中的這個(gè)方法序列化所有字段睡陪,如果有的話就執(zhí)行你自己實(shí)現(xiàn)的這個(gè)方法。
?????接下來匿情,看看這兩個(gè)方法實(shí)現(xiàn)的細(xì)節(jié)宝穗,先看writeObject方法,參數(shù)是ObjectOutputStream 類型的码秉,這個(gè)拿到的是我們在main方法中定義的ObjectOutputStream 對象,要不然它怎么知道該把對象寫到那個(gè)地方去呢鸡号?第一行我們調(diào)用的是oos.defaultWriteObject();這個(gè)方法實(shí)現(xiàn)的功能是转砖,將當(dāng)前對象中所有沒有被transient修飾的字段寫入流中,第二條語句我們顯式的調(diào)用了writeInt方法將age的值寫入流中鲸伴。讀取的方法類似府蔗,此處不再贅述。

五汞窗、版本控制
?????最后我們來看看姓赤,序列化過程的的版本控制問題。在我們將一個(gè)對象序列化到流中之后仲吏,該對象對應(yīng)的類的結(jié)構(gòu)改變了不铆,如果此時(shí)我們再次從流中將之前保存的對象讀取出來蝌焚,會(huì)發(fā)生什么?這要分情況來說誓斥,如果原類中的字段被刪除了只洒,那從流中輸出的對應(yīng)的字段將會(huì)被忽略。如果原類中增加了某個(gè)字段劳坑,那新增的字段的值就是默認(rèn)值毕谴。如果字段的類型發(fā)生了改變,拋出異常距芬。在Java中每個(gè)類都會(huì)有一個(gè)記錄版本號(hào)的變量:static final serivalVersionUID = 115616165165L涝开,此處的值只用于演示并不對應(yīng)任意某個(gè)類。這個(gè)版本號(hào)是根據(jù)該類中的字段等一些屬性信息計(jì)算出來的框仔,唯一性較高舀武。每次讀出的時(shí)候都會(huì)去比較之前和現(xiàn)在的版本號(hào)確認(rèn)是否發(fā)生版本不一致情況,如果版本不一致存和,就會(huì)按照上述的情形分別做處理奕剃。

?????對象的序列化就寫完了,如果有什么內(nèi)容不妥的地方捐腿,希望大家指出纵朋!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市茄袖,隨后出現(xiàn)的幾起案子操软,更是在濱河造成了極大的恐慌,老刑警劉巖宪祥,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聂薪,死亡現(xiàn)場離奇詭異,居然都是意外死亡蝗羊,警方通過查閱死者的電腦和手機(jī)藏澳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耀找,“玉大人翔悠,你說我怎么就攤上這事∫懊ⅲ” “怎么了蓄愁?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狞悲。 經(jīng)常有香客問我撮抓,道長,這世上最難降的妖魔是什么摇锋? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任丹拯,我火速辦了婚禮站超,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咽笼。我一直安慰自己顷编,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布剑刑。 她就那樣靜靜地躺著媳纬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪施掏。 梳的紋絲不亂的頭發(fā)上钮惠,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音七芭,去河邊找鬼素挽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狸驳,可吹牛的內(nèi)容都是我干的预明。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耙箍,長吁一口氣:“原來是場噩夢啊……” “哼撰糠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辩昆,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤阅酪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后汁针,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體术辐,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年施无,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辉词。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猾骡,死狀恐怖较屿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卓练,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布购啄,位于F島的核電站襟企,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狮含。R本人自食惡果不足惜顽悼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一曼振、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔚龙,春花似錦冰评、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坑填,卻和暖如春抛人,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脐瑰。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工妖枚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苍在。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓绝页,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寂恬。 傳聞我的和親對象是個(gè)殘疾皇子续誉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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

  • JAVA序列化機(jī)制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時(shí)候,保證對象的完整...
    時(shí)待吾閱讀 10,863評論 0 24
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化,必須實(shí)現(xiàn)Serializable接口掠剑。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,409評論 1 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法屈芜,類相關(guān)的語法,內(nèi)部類的語法朴译,繼承相關(guān)的語法井佑,異常的語法,線程的語...
    子非魚_t_閱讀 31,625評論 18 399
  • 對象序列化的目標(biāo): 將對的字節(jié)序列對象永久的保存到磁盤中眠寿。 允許在網(wǎng)絡(luò)上直接傳輸對象躬翁,傳輸對象的字節(jié)序列。 對象序...
    年少懵懂丶流年夢閱讀 1,165評論 0 3
  • 一盯拱、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程盒发;反序列化de...
    步積閱讀 1,441評論 0 10