Java:序列化和反序列化

1.背景

某天幕袱,我在寫代碼定義 bean 的時候铐拐,順手寫了個 public class User implements Serializable,旁邊的小哥哥看到了問我:你為什么要實現(xiàn) Serializable 接口坦敌?你哪里用到它了嗎酵镜?不實現(xiàn)這個接口可以嗎?

emmm殴穴,皺眉沉思一下凉夯,好像也可以?

好吧采幌,那先來了解一下 Serializable 接口涉及到的相關(guān)概念劲够。

2.序列化協(xié)議+序列化和反序列化

  • 序列化是指:將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成特定的格式,使其可以在網(wǎng)絡(luò)中傳輸休傍,或可存儲在內(nèi)存/文件中征绎。

序列化后的數(shù)據(jù)必須是可保持或可傳輸?shù)臄?shù)據(jù)格式,例如:二進制串/字節(jié)流磨取、XML人柿、JSON等。

  • 反序列化:是序列化的逆過程忙厌,將對象從序列化數(shù)據(jù)中還原出來凫岖。

自問自答

  • 問:序列化的目的是什么?
  • 答:方便的進行數(shù)據(jù)的交換和傳輸工作逢净。

3.JDK類庫中的序列化API

Java本身提供了對數(shù)據(jù)/對象序列化的支持哥放。

  • 輸入輸出流

    • ObjectOutputStream 對象輸出流,其 writeObject(Object obj) 方法可對參數(shù)指定的 obj 對象進行序列化爹土,把得到的字節(jié)序列寫到一個目標輸出流中甥雕。
    • ObjectInputStream 對象輸入流,其 readObject() 方法從一個源輸入流中讀取字節(jié)序列胀茵,再把它們反序列化為一個對象社露,并將其返回。
  • 接口

    • 只有實現(xiàn)了 SerializableExternalizable 接口的類的對象才能被序列化琼娘。
    • Externalizable 接口繼承自 Serializable 接口峭弟。
    • 實現(xiàn) Externalizable 接口的類完全由自身來控制序列化的行為赁濒;而僅實現(xiàn) Serializable 接口的類可以采用默認的序列化方式 。
  • 對象序列化步驟:

    • 創(chuàng)建一個對象輸出流孟害。
    • 通過對象輸出流的 writeObject() 方法寫對象拒炎。
  • 對象反序列化步驟:

    • 創(chuàng)建一個對象輸入流。
    • 通過對象輸入流的 readObject() 方法讀取對象挨务。

3.1 對象序列化到文件

// User.java
package com.ann.javas.javacores.serialization.demo1;

import java.io.Serializable;


public class User implements Serializable{

    private static String HH="我是靜態(tài)變量击你,我不會被序列化";
    private int userId;
    private String userName;
    private String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public static String getHH() {
        return HH;
    }

    public static void setHH(String HH) {
        User.HH = HH;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

// Client.java
package com.ann.javas.javacores.serialization.demo1;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令習(xí)習(xí)習(xí)");
        user.setAddress("北京");

        System.out.println("對象:"+user.toString());
        System.out.println("對象中的靜態(tài)變量:"+user.getHH());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        User tmp = new User();
        tmp.setHH("我是靜態(tài)變量,我的值是存在JVM靜態(tài)存儲區(qū)的谎柄,不是反序列化來的");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("對象:"+user.toString());
        System.out.println("對象中的靜態(tài)變量:"+user.getHH());
        ois.close();
    }
}

運行結(jié)果:

對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
對象中的靜態(tài)變量:我是靜態(tài)變量丁侄,我不會被序列化
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
對象中的靜態(tài)變量:我是靜態(tài)變量,我的值是存在JVM靜態(tài)存儲區(qū)的朝巫,不是反序列化來的
  • 這是一個簡單的序列化和反序列化例子鸿摇,創(chuàng)建一個 User 實例,將其全部數(shù)據(jù)序列化到文件劈猿;然后再從文件讀取數(shù)據(jù)反序列化為對象拙吉。

  • 需要特別關(guān)注的是:對象序列化保存的是對象的"狀態(tài)",即它的成員變量揪荣。因此筷黔,對象序列化不會關(guān)注類中的靜態(tài)變量。

3.2 隱藏指定字段

在某些場景下仗颈,你希望某些字段不要被序列化佛舱,此時可以使用 transient 關(guān)鍵字來進行排除。

  • transient 關(guān)鍵字只修飾變量挨决,不修飾方法和類请祖。
  • transient 關(guān)鍵字修飾的變量不再能被序列化,自然也不會被反序列化回來脖祈。
// User.java
package com.ann.javas.javacores.serialization.demo2;

import java.io.Serializable;


public class User implements Serializable{

    private int userId;
    private String userName;
    private transient String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}


//Client.java
package com.ann.javas.javacores.serialization.demo2;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令習(xí)習(xí)習(xí)");
        user.setAddress("北京");

        System.out.println("對象:"+user.toString());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("對象:"+user.toString());
        ois.close();
    }
}

運行結(jié)果:

對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='null'}

這里使用 transient 修飾了 Useraddress 變量肆捕,因此address不會被序列化,也不會被反序列化撒犀。

自問自答

  • 問:使用 transient 修飾的變量福压,就一定不會被序列化了嗎?
  • 答:不一定或舞,要取決于你的程序是怎么寫的。

3.3 Serializable 的 readObject 和 writeObject

// User.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class User implements Serializable{

    private int userId;
    private String userName;
    private transient String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(address);
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        address = (String)in.readObject();
    }
}

// Client.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令習(xí)習(xí)習(xí)");
        user.setAddress("北京");

        System.out.println("對象:"+user.toString());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("對象:"+user.toString());
        ois.close();
    }
}


運行結(jié)果:

對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}

在這個例子中蒙幻,User 定義了兩個private方法:readObject()writeObject() 映凳。

writeObject() 方法中會先調(diào)用 ObjectOutputStream 中的 defaultWriteObject() 方法,該方法會執(zhí)行默認的序列化機制邮破,此時會忽略掉被 transient 修飾的address字段诈豌。然后再調(diào)用 writeObject() 方法顯示地將address字段寫入到 ObjectOutputStream 中仆救。

readObject() 的作用則是針對對象的讀取,其原理與 writeObject() 方法相同矫渔。

3.4 實現(xiàn) Externalizable 接口

在Java中彤蔽,對象的序列化可以通過實現(xiàn)兩種接口來實現(xiàn):

  • 若實現(xiàn)的是 Serializable 接口,則所有的序列化將會自動進行庙洼,如果你希望在此基礎(chǔ)之上加點自定義的內(nèi)容顿痪,就可以像上面那樣加兩個方法就ok了。
  • 若實現(xiàn)的是 Externalizable 接口油够,則沒有任何東西可以自動序列化蚁袭,需要在
    writeExternal() 方法中進行手工指定所要序列化的變量,以及如何序列化石咬,這與是否被 transient 修飾無關(guān)(也就是說揩悄,當你不需要java自動為你序列化的時候,transient就失效了)鬼悠;當然 readExternal() 也需要做相應(yīng)的處理删性。
// User.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.*;


public class User implements Externalizable{

    private int userId;
    private String userName;
    private transient String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(userId + 122);
        out.writeObject(userName);
        out.writeObject(address);
        System.out.println("writeExternal:我沒有存原文哦");
        out.flush();
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userId = in.readInt();
        userName = (String)in.readObject();
        address = (String)in.readObject();
    }
}

// Client.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令習(xí)習(xí)習(xí)");
        user.setAddress("北京");

        System.out.println("對象:"+user.toString());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("對象:"+user.toString());
        ois.close();
    }
}

運行結(jié)果:

對象:User{userId=1223, userName='令習(xí)習(xí)習(xí)', address='北京'}
writeExternal:我沒有存原文哦
序列化成功
反序列化成功
對象:User{userId=1345, userName='令習(xí)習(xí)習(xí)', address='北京'}

這里有幾個關(guān)鍵單需要說明:

  • 實現(xiàn) Externalizable 接口,一定要自定義序列化方法焕窝,如果你把 writeExternal()readExternal() 這里面的實現(xiàn)都丟掉镇匀,就會發(fā)現(xiàn),java真的什么都不會做袜啃。
  • 如上面所說汗侵,當實現(xiàn)了 Externalizable 接口的時候,transient 關(guān)鍵字不再生效群发。
  • 反序列化時晰韵,實際上調(diào)用了 User 的無參構(gòu)造函數(shù),因此在自定義序列化方案的時候熟妓,請一定要記得提供一個 公共無參構(gòu)造函數(shù) 雪猪,不然就悲劇了。

4.關(guān)于 Serializable 和 Externalizable 的總結(jié)和附加說明

  • 構(gòu)造器:

    • Serializable 序列化時不會調(diào)用默認的構(gòu)造器起愈;
    • Externalizable 序列化時會調(diào)用默認構(gòu)造器只恨。
  • 功能

    • 一個對象想要被序列化,那么它的類就要實現(xiàn) Serializable 接口抬虽,這個對象的所有屬性(包括private屬性官觅、包括其引用的對象)都可以被序列化和反序列化來保存、傳遞阐污。
    • ExternalizableSerializable 接口的子類休涤,有時我們不希望序列化那么多,可以使用這個接口,這個接口的 writeExternal()readExternal() 方法可以指定序列化哪些屬性功氨。
  • 關(guān)鍵字

    • 由于 Externalizable 對象默認不保存對象的任何字段序苏,所以 transient 關(guān)鍵字只能伴隨 Serializable 使用,雖然 Externalizable 對象中使用 transient 關(guān)鍵字也不報錯捷凄,但不起任何作用忱详。
  • 方法

    • Serializable 接口的 writeObject()readObject() 方法是可選實現(xiàn),若沒有自定義跺涤,則使用默認的匈睁。
    • Externalizable 接口的 writeExternal()readExternal() 方法是必選實現(xiàn),當然你可以在里面什么都不做钦铁。

自問自答

  • 問: writeObject()readObject() 都是private方法软舌,它們是怎么被調(diào)用的呢?
  • 答:很顯然牛曹,反射佛点。詳情可見 ObjectOutputStream 中的 writeSerialData 方法,以及 ObjectInputStream 中的 readSerialData 方法黎比。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末超营,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阅虫,更是在濱河造成了極大的恐慌演闭,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颓帝,死亡現(xiàn)場離奇詭異米碰,居然都是意外死亡,警方通過查閱死者的電腦和手機购城,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門吕座,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘪板,你說我怎么就攤上這事吴趴。” “怎么了侮攀?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵锣枝,是天一觀的道長。 經(jīng)常有香客問我兰英,道長撇叁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任箭昵,我火速辦了婚禮税朴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘家制。我一直安慰自己正林,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布颤殴。 她就那樣靜靜地躺著觅廓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涵但。 梳的紋絲不亂的頭發(fā)上杈绸,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音矮瘟,去河邊找鬼瞳脓。 笑死,一個胖子當著我的面吹牛澈侠,可吹牛的內(nèi)容都是我干的劫侧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼哨啃,長吁一口氣:“原來是場噩夢啊……” “哼烧栋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拳球,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤审姓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后祝峻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魔吐,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年莱找,在試婚紗的時候發(fā)現(xiàn)自己被綠了酬姆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡宋距,死狀恐怖轴踱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谚赎,我是刑警寧澤淫僻,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站壶唤,受9級特大地震影響雳灵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闸盔,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一悯辙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦躲撰、人聲如沸针贬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桦他。三九已至,卻和暖如春谆棱,著一層夾襖步出監(jiān)牢的瞬間快压,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工垃瞧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蔫劣,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓个从,卻偏偏與公主長得像脉幢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子信姓,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

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

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,842評論 0 24
  • 1. Java序列化和反序列化(What) Java序列化(Serialize)是指將一個Java對象寫入IO流中...
    悠揚前奏閱讀 868評論 2 1
  • 什么是序列化鸵隧? 序列化是將對象存儲為二進制格式。在序列化的過程中意推,對象和它的元數(shù)據(jù)(比如對象的類名和它的屬性名稱)...
    Chokez閱讀 1,101評論 0 0
  • 對象序列化(serialization)和反序列化(deserialization)是將對象轉(zhuǎn)化為便于傳輸?shù)母袷竭M...
    JerryL_閱讀 7,526評論 1 7
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化豆瘫,必須實現(xiàn)Serializable接口。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,395評論 1 3