Java序列化基礎(chǔ)

聲明:原創(chuàng)文章愉豺,轉(zhuǎn)載請(qǐng)注明出處外潜。http://www.reibang.com/u/e02df63eaa87

1、序列化概念

序列化:把對(duì)象轉(zhuǎn)換為字節(jié)序列的過程友绝。
反序列化:把字節(jié)序列恢復(fù)為對(duì)象的過程抛腕。

對(duì)象的序列化主要有兩種用途

  • 把對(duì)象的字節(jié)序列永久地保存到硬盤上芋绸,通常存放在一個(gè)文件中媒殉;
  • 在網(wǎng)絡(luò)上傳送對(duì)象的字節(jié)序列担敌。

在很多應(yīng)用中,需要對(duì)某些對(duì)象進(jìn)行序列化廷蓉,讓它們離開內(nèi)存空間全封,入住物理硬盤,以便長(zhǎng)期保存桃犬。
當(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ì)象就轧。

2、JDK中的序列化API

2.1 輸入輸出流

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ì)象,并將其返回。
  
對(duì)象序列化包括如下步驟:

  • 創(chuàng)建一個(gè)對(duì)象輸出流惋啃,它可以包裝一個(gè)其他類型的目標(biāo)輸出流哼鬓,如文件輸出流;
  • 通過對(duì)象輸出流的writeObject()方法寫對(duì)象边灭。

對(duì)象反序列化的步驟如下:

  • 創(chuàng)建一個(gè)對(duì)象輸入流魄宏,它可以包裝一個(gè)其他類型的源輸入流,如文件輸入流存筏;
  • 通過對(duì)象輸入流的readObject()方法讀取對(duì)象宠互。
2.2 Serializable接口

并不是每個(gè)對(duì)象都能寫到輸出流⊥旨幔可以寫入輸出流的對(duì)象稱為可序列化的予跌。可序列化的對(duì)象是java.io.Serializable接口的實(shí)例善茎。因此券册,可序列化對(duì)象必須實(shí)現(xiàn)Serializable接口。試圖存儲(chǔ)一個(gè)不支持Serializable接口的對(duì)象會(huì)引起一個(gè)NotSerializableException異常垂涯。

Serializable接口是一種標(biāo)記接口烁焙。由于它沒有任何方法,不需要在類中為實(shí)現(xiàn)Serializable接口增加代碼耕赘。實(shí)現(xiàn)這個(gè)接口可以啟動(dòng)Java的序列化機(jī)制骄蝇,自動(dòng)完成存儲(chǔ)對(duì)象和數(shù)組的過程。

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

2.3 例子

Person類

public class Person implements Serializable {
    private int id;
    private int age;
    private boolean sex;
    private String name;
    private String addr;

    public Person(int id, int age, boolean sex, String name, String addr) {
        this.id = id;
        this.age = age;
        this.sex = sex;
        this.name = name;
        this.addr = addr;
    }

    public Person() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public boolean isSex() {
        return sex;
    }

    public void setSex(boolean sex) {
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

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

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", age=" + age +
                ", sex=" + sex +
                ", name='" + name + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }
}

TestSerializable 類

public class TestSerializable {

    public static void main(String[] args) {
        try {
            Person person = new Person(1001, 18, true, "Jack", "BeiJing");
            // 序列化
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Person.dat")));
            oos.writeObject(person);
            oos.close();

            // 反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Person.dat")));
            Person p = (Person) ois.readObject();
            System.out.println(p.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運(yùn)行結(jié)果

運(yùn)行結(jié)果

2.4 transient關(guān)鍵字
  • 如果一個(gè)對(duì)象是Serializable實(shí)例岔激,但包含了非序列化的成員,那這個(gè)對(duì)象是不可以被序列化的是掰。
  • 如果一個(gè)對(duì)象序列化時(shí)虑鼎,對(duì)于其中的某個(gè)對(duì)象不希望其被序列化(敏感信息),即使對(duì)象中的成員是私有的键痛,通過序列化處理就可以通過文件或攔截網(wǎng)絡(luò)來訪問炫彩。

有一種方法可以防止敏感信息被序列化,將類實(shí)現(xiàn)為Externalizable散休,這樣媒楼,沒有任何東西可以自動(dòng)序列化,并且可以在writeExternal()內(nèi)部只對(duì)所需部分進(jìn)行顯式的序列化戚丸。

如果操作的是一個(gè)Serializable對(duì)象划址,序列化操作會(huì)自動(dòng)進(jìn)行扔嵌。為了能夠控制,可以使用transient關(guān)鍵字逐個(gè)字段關(guān)閉序列化夺颤。

例子:

public class Login implements Serializable {
    private String username;
    private Date date = new Date();
    private transient String passuid;

    public Login(String username, String passuid) {
        this.username = username;
        this.passuid = passuid;
    }

    @Override
    public String toString() {
        return "Login{" +
                "date=" + date +
                ", username='" + username + '\'' +
                ", passuid='" + passuid + '\'' +
                '}';
    }

    public static void main(String[] args) throws Exception {
        Login login = new Login("Jack", "123456");
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Login.dat")));
        oos.writeObject(login);
        oos.close();

        TimeUnit.SECONDS.sleep(1);
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Login.dat")));
        Login a = (Login) ois.readObject();
        System.out.println(a.toString());
    }
}

可以看到痢缎,其中的date和username域是可見的,而passuid是transient的世澜,所以不會(huì)被自動(dòng)保存到磁盤独旷。另外,自動(dòng)序列化機(jī)制也不會(huì)嘗試去恢復(fù)它寥裂,當(dāng)對(duì)象被恢復(fù)時(shí)嵌洼,passuid域就會(huì)變成null。
由于Externalizable對(duì)象在默認(rèn)情況下不保存任何字段封恰,所以transient關(guān)鍵字只能和Serializable對(duì)象一起使用麻养。

2.5 serialVersionUID的作用

s?e?r?i?a?l?V?e?r?s?i?o?n?U?I?D?:? ?字?面?意?思?上?是?序?列?化?的?版?本?號(hào)?,凡是實(shí)現(xiàn)Serializable接口的類都有一個(gè)表示序列化版本標(biāo)識(shí)符的靜態(tài)變量诺舔。

private static final long serialVersionUID = 4603642343377807741L;

對(duì)于上面Login的例子鳖昌,增加private String ip;,在對(duì)Login.dat進(jìn)行反序列化:

public class Login implements Serializable {
    private String username;
    private Date date = new Date();
    private transient String passuid;
    private String ip;

    public Login(String username, String passuid) {
        this.username = username;
        this.passuid = passuid;
    }

    @Override
    public String toString() {
        return "Login{" +
                "date=" + date +
                ", username='" + username + '\'' +
                ", passuid='" + passuid + '\'' +
                '}';
    }

    public static void main(String[] args) throws Exception {
        Login login = new Login("Jack", "123456");
        // 序列化
        // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Login.dat")));
        // oos.writeObject(login);
        // oos.close();

        TimeUnit.SECONDS.sleep(1);
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Login.dat")));
        Login a = (Login) ois.readObject();
        System.out.println(a.toString());
    }
}
Exception in thread "main" java.io.InvalidClassException: gc.Login; local class incompatible: stream classdesc serialVersionUID = -4069397356839580213, local class serialVersionUID = 3104314666871190943
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    ...

序列化運(yùn)行時(shí)使用serialVersionUID 版本號(hào)與每個(gè)可序列化類相關(guān)聯(lián)低飒,該序列號(hào)在反序列化過程中用于驗(yàn)證序列化對(duì)象的發(fā)送者和接收者是否為該對(duì)象加載了與序列化兼容的類许昨。如果接收者加載的該對(duì)象的類的 serialVersionUID 與對(duì)應(yīng)的發(fā)送者的類的版本號(hào)不同,則反序列化將會(huì)導(dǎo)致 InvalidClassException異常褥赊。

如果可序列化類未顯式聲明 serialVersionUID糕档,則序列化運(yùn)行時(shí)將基于該類的各個(gè)方面計(jì) 算該類的默認(rèn) serialVersionUID 值。不過崭倘,強(qiáng)烈建議所有可序列化類都顯式聲明 serialVersionUID 值翼岁,原因是計(jì)算默認(rèn)的 serialVersionUID 對(duì)類的詳細(xì)信息具有較高的敏感性,根據(jù)編譯器實(shí)現(xiàn)的不同可能千差萬別司光,這樣 在反序列化過程中可能會(huì)導(dǎo)致意外的 InvalidClassException。因此悉患,為保證 serialVersionUID 值跨不同 java 編譯器實(shí)現(xiàn)的一致性残家,序列化類必須聲明一個(gè)明確的 serialVersionUID 值。

因此售躁,指定serialVersionUID的值坞淮,重新進(jìn)行序列化:

public class Login implements Serializable {
    
    private static final long serialVersionUID = 4603642343377807741L;
    private String username;
    private Date date = new Date();
    private transient String passuid;

    public Login(String username, String passuid) {
        this.username = username;
        this.passuid = passuid;
    }

    @Override
    public String toString() {
        return "Login{" +
                "date=" + date +
                ", username='" + username + '\'' +
                ", passuid='" + passuid + '\'' +
                '}';
    }

    public static void main(String[] args) throws Exception {
        Login login = new Login("Jack", "123456");
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Login.dat")));
        oos.writeObject(login);
        oos.close();
}

加入新的字段,再次進(jìn)行反序列化:

public class Login implements Serializable {
    private static final long serialVersionUID = 4603642343377807741L;

    private String username;
    private Date date = new Date();
    private transient String passuid;
    private String ip;

    public Login(String username, String passuid) {
        this.username = username;
        this.passuid = passuid;
    }

    @Override
    public String toString() {
        return "Login{" +
                "date=" + date +
                ", username='" + username + '\'' +
                ", passuid='" + passuid + '\'' +
                '}';
    }

    public static void main(String[] args) throws Exception {
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Login.dat")));
        Login a = (Login) ois.readObject();
        System.out.println(a.toString());
    }
}

可以看到陪捷,Login{date=Thu Mar 09 09:27:35 CST 2017, username='Jack', passuid='null'}回窘,說明反序列化成功了。

顯式地定義serialVersionUID有兩種用途:

  • 在某些場(chǎng)合市袖,希望類的不同版本對(duì)序列化兼容啡直,因此需要確保類的不同版本具有相同的serialVersionUID烁涌;
  • 在某些場(chǎng)合,不希望類的不同版本對(duì)序列化兼容酒觅,因此需要確保類的不同版本具有不同的serialVersionUID撮执。

引用
http://286.iteye.com/blog/2227942
http://www.cnblogs.com/xdp-gacl/p/3777987.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舷丹,隨后出現(xiàn)的幾起案子抒钱,更是在濱河造成了極大的恐慌,老刑警劉巖颜凯,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谋币,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡症概,警方通過查閱死者的電腦和手機(jī)瑞信,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穴豫,“玉大人凡简,你說我怎么就攤上這事【啵” “怎么了秤涩?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)司抱。 經(jīng)常有香客問我筐眷,道長(zhǎng),這世上最難降的妖魔是什么习柠? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任匀谣,我火速辦了婚禮,結(jié)果婚禮上资溃,老公的妹妹穿的比我還像新娘武翎。我一直安慰自己,他們只是感情好溶锭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布宝恶。 她就那樣靜靜地躺著,像睡著了一般趴捅。 火紅的嫁衣襯著肌膚如雪垫毙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天拱绑,我揣著相機(jī)與錄音综芥,去河邊找鬼。 笑死猎拨,一個(gè)胖子當(dāng)著我的面吹牛膀藐,可吹牛的內(nèi)容都是我干的屠阻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼消请,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼栏笆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臊泰,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蛉加,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缸逃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體针饥,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年需频,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丁眼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昭殉,死狀恐怖苞七,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挪丢,我是刑警寧澤蹂风,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站乾蓬,受9級(jí)特大地震影響惠啄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜任内,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一撵渡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧死嗦,春花似錦趋距、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廊敌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間门怪,已是汗流浹背骡澈。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掷空,地道東北人肋殴。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓囤锉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親护锤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子官地,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,863評(píng)論 0 24
  • 原帖地址:原帖個(gè)人網(wǎng)站地址:個(gè)人網(wǎng)站簡(jiǎn)書對(duì)markdown的支持太完美了,我竟然可以直接Ctrl C/V過來烙懦。 定...
    ryderchan閱讀 3,800評(píng)論 1 9
  • 如果你只知道實(shí)現(xiàn) Serializable 接口的對(duì)象驱入,可以序列化為本地文件。那你最好再閱讀該篇文章氯析,文章對(duì)序列化...
    jiangmo閱讀 475評(píng)論 0 2
  • 一亏较、 序列化和反序列化概念 Serialization(序列化)是一種將對(duì)象以一連串的字節(jié)描述的過程;反序列化de...
    步積閱讀 1,443評(píng)論 0 10
  • 讀經(jīng)時(shí)間: 2017年3月30日 星期四 晴轉(zhuǎn)陰 讀經(jīng)人員: 媽媽 讀經(jīng)內(nèi)容: 復(fù)習(xí)《易經(jīng)》63掩缓、64卦;《詩...
    161d968e601f閱讀 117評(píng)論 0 0