java序列化與反序列化

原帖地址:原帖
個人網(wǎng)站地址:個人網(wǎng)站
簡書對markdown的支持太完美了袄膏,我竟然可以直接Ctrl C/V過來。


定義

Java序列化是指把Java對象轉(zhuǎn)換為字節(jié)序列的過程哎媚;

Java反序列化是指把字節(jié)序列恢復(fù)為Java對象的過程忽你。

應(yīng)用場景

當(dāng)兩個進(jìn)程進(jìn)行遠(yuǎn)程通信時,可以相互發(fā)送各種類型的數(shù)據(jù)颖系,包括文本、圖片辩越、音頻嘁扼、視頻等, 而這些數(shù)據(jù)都會以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送黔攒。那么當(dāng)兩個Java進(jìn)程進(jìn)行通信時趁啸,如何實(shí)現(xiàn)進(jìn)程間的對象傳送呢?這就需要Java序列化與反序列化了督惰。一方面不傅,發(fā)送方需要把這個Java對象轉(zhuǎn)換為字節(jié)序列,然后在網(wǎng)絡(luò)上傳送赏胚;另一方面访娶,接收方需要從字節(jié)序列中恢復(fù)出Java對象。數(shù)據(jù)傳輸便是序列化與反序列化的主要應(yīng)用場景之一觉阅。當(dāng)然也可用于數(shù)據(jù)的存儲與讀取震肮。

實(shí)現(xiàn)方式

java的序列化有兩種方式:

  1. 實(shí)現(xiàn)序列化接口Serializable,這個使用的比較多留拾。Serializable接口是一個空的接口戳晌,它的主要作用就是標(biāo)識這個類的對象是可序列化的。
  2. 實(shí)現(xiàn)接口Externalizable痴柔。Exterinable繼承了Serializable沦偎,是Serializable的一個擴(kuò)展,對于哪些屬性可以序列化咳蔚,哪些可以反序列化可以做詳細(xì)地約束豪嚎。

相關(guān)工具類:

  1. java.io.ObjectOutputStream:表示對象輸出流

    它是OutputStream類的一個子類,對應(yīng)的ObjectOutputStream.WriteObject(Object object)就要求參數(shù)object實(shí)現(xiàn)Serializable接口谈火。

    使用方式如下:

    import java.io.FileOutputStream;
    import java.io.ObjectOutputStream;
    
    String fileName = "test.txt";  //文件名
    LoginInfo info = new LoginInfo("chen","123"); //某個待序列化對象侈询,LoginInfo類須實(shí)現(xiàn)Serializable接口
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName)); //創(chuàng)建輸出流
    oos.writeObject(info);   //序列化
    oos.close();   //關(guān)閉流
    
  2. java.io.ObjectInputStream:表示對象輸入流

    相應(yīng)地,它的readObject(Object object)方法從輸入流中讀取字節(jié)序列糯耍,再把它們反序列化成為一個對象扔字,并返回囊嘉。

    使用方式如下:

    import java.io.FileInputStream;
    import java.io.ObjectInputStream;
    
    String fileName = "test.txt";
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
    LoginInfo info2 = (LoginInfo)ois.readObject();
    ois.close();
    

方式一:實(shí)現(xiàn)Serializable接口

Serializable源碼

/*
 * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package java.io;
/**
 * Serializability of a class is enabled by the class implementing the
 * java.io.Serializable interface. Classes that do not implement this
 * interface will not have any of their state serialized or
 * deserialized.  All subtypes of a serializable class are themselves
 * serializable.  The serialization interface has no methods or fields
 * and serves only to identify the semantics of being serializable.
 *
 * @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

中間還省略了很多注釋,再忽略上面留下的注釋革为,發(fā)現(xiàn)這個接口是空的扭粱!沒有字段,沒有方法震檩,只是標(biāo)識一個類的對象是否可序列化(實(shí)現(xiàn)了這個接口琢蛤,就表示可以序列化)。

serialVersionUID的作用

實(shí)現(xiàn)Serializable接口前后并沒有增加新方法抛虏,只是多了一個serialVersionUID博其,其實(shí)這個字段也不是必須的。若不寫serialVersionUID迂猴。程序也能運(yùn)行成功慕淡,不過eclipe會顯示警告。

其實(shí)错忱,Java的序列化機(jī)制是通過在運(yùn)行時判斷類的serialVersionUID來驗(yàn)證版本一致性的。在進(jìn)行反序列化時挂据,JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體類的serialVersionUID進(jìn)行比較以清,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化崎逃,否則就會出現(xiàn)序列化版本不一致的異常(InvalidCastException)掷倔。

所以最好有這個字段,那么要如何添加呢个绍?鼠標(biāo)移動到警告處勒葱,eclipse在給出警告的同時也給出了解決方法:

  1. 添加默認(rèn)值,即1L;
  2. 添加自動產(chǎn)生的ID值巴柿,我的是8685376332791485990L;(推薦)
  3. 通過@SuppressWarnings 批注取消特定代碼段(即凛虽,類或方法)中的警告。

java實(shí)現(xiàn)

package serialize;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;

public class LoginInfo implements Serializable{
    private static final long serialVersionUID = 8685376332791485990L;
    private String username;
    private String password;
    private Date logindate;
    public LoginInfo(){
        System.out.println("non-parameter constructor");
    }
    public LoginInfo(String username, String password){
        System.out.println("parameter constructor");
        this.username = username;
        this.password = password;
        this.logindate = new Date();
    }
    public String toString(){
        return "username="+username+",password="+password+",logindate="+logindate;
    }
    public static void main(String[] args) throws Exception{
        LoginInfo info = new LoginInfo("chen","123");
        System.out.println(info);
        String fileName = "info_serializable.txt";
        System.out.println("Serialize object");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
        oos.writeObject(info);
        oos.close();
        System.out.println("Deserialize object");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
        LoginInfo info2 = (LoginInfo)ois.readObject();
        ois.close();
        System.out.println(info2);
    }
}

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

parameter constructor
username=chen,password=123,logindate=Sun Feb 19 09:33:38 CST 2017
Serialize object
Deserialize object
username=chen,password=123,logindate=Sun Feb 19 09:33:38 CST 2017

之后會在項(xiàng)目目錄下生成文件info_serializable.txt广恢,雖然存成了txt格式凯旋,但并不能使用普通文本格式打開,會出現(xiàn)亂碼钉迷。

transicent的作用

如果至非,不希望某些字段被序列化,如LoginInfo中的username糠聪,只需在對應(yīng)字段的定義時使用關(guān)鍵詞transient:

// private String password;
private transient String password;

再次運(yùn)行程序荒椭,結(jié)果如下:

parameter constructor
username=chen,password=123,logindate=Sun Feb 19 09:50:34 CST 2017
Serialize object
Deserialize object
username=chen,password=null,logindate=Sun Feb 19 09:50:34 CST 2017

password反序列化后的結(jié)果為null,達(dá)到了保護(hù)密碼的目的舰蟆。

方式二:實(shí)現(xiàn)Externalizable接口

Externalizable源碼

/*
 * Copyright (c) 1996, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;

/**
 * @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Serializable
 * @since   JDK1.1
 */
public interface Externalizable extends java.io.Serializable {
    /**
     * @param out the stream to write the object to
     * @exception IOException Includes any I/O exceptions that may occur
     */
    void writeExternal(ObjectOutput out) throws IOException;
    /**
     * @param in the stream to read data from in order to restore the object
     * @exception IOException if I/O errors occur
     * @exception ClassNotFoundException If the class for an object being
     *              restored cannot be found.
     */
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

Externalizable繼承了Serializable接口趣惠,并添加了兩個新的方法狸棍,分別表示在哪些字段能被序列化和哪些字段能夠反序列化。

java實(shí)現(xiàn)

erialVersionUID和之前一樣信卡,最好添加隔缀。(但不添加的話,eclipse竟然連警告都沒有0健猾瘸!無奈,只能自己隨便寫個數(shù)了丢习。)

package serialize;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Date;

public class LoginInfo2 implements Externalizable{
    private static final long serialVersionUID = 4297291454171868241L;
    private String username;
    private String password;
    private Date logindate;
    public LoginInfo2(){
        System.out.println("non-parameter constructor");
    }
    public LoginInfo2(String username, String password){
        System.out.println("parameter constructor");
        this.username = username;
        this.password = password;
        this.logindate = new Date();
    }
    public String toString(){
        return "username="+username+
                ",password="+password+
                ",logindate="+logindate;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(logindate);
        out.writeUTF(username);
        
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        logindate = (Date)in.readObject();
        username = (String)in.readUTF();        
    }
    
    public static void main(String[] args) throws Exception{
        LoginInfo2 info = new LoginInfo2("chen","123");
        System.out.println(info);
        String fileName = "info_externalizable.txt";
        
        System.out.println("Serialize object");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
        oos.writeObject(info);
        oos.close();
        System.out.println("Deserialize object");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
        LoginInfo2 info2 = (LoginInfo2)ois.readObject();
        ois.close();
        System.out.println(info2);
    }
}

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

parameter constructor
username=chen,password=123,logindate=Sun Feb 19 10:53:57 CST 2017
Serialize object
Deserialize object
non-parameter constructor
username=chen,password=null,logindate=Sun Feb 19 10:53:57 CST 2017

實(shí)現(xiàn)了和Serializable+transient同樣的目的牵触。

無參構(gòu)造函數(shù)

仔細(xì)分析運(yùn)行結(jié)果,發(fā)現(xiàn)這種方式反序列化的時候竟然調(diào)用了無參構(gòu)造函數(shù)咐低。對于恢復(fù)Serializable對象揽思,完全以它存儲的二進(jìn)制為基礎(chǔ)來構(gòu)造,而不調(diào)用構(gòu)造函數(shù)见擦。而對于一個Externalizable對象钉汗,public的無參構(gòu)造函數(shù)將會被調(diào)用。如果沒有public的無參構(gòu)造函數(shù)鲤屡,運(yùn)行時會報異常(Invalid Class Exception : LoginInfo2 ; no valid constructor...),之后會調(diào)用readExternal 讀取數(shù)據(jù)损痰。源碼的注釋中也做了如下說明:

Object Serialization uses the Serializable and Externalizable interfaces. Object persistence mechanisms can use them as well. Each object to be stored is tested for the Externalizable interface.

  • If the object supports Externalizable, the writeExternal method is called. If the object does not support Externalizable and does implement Serializable, the object is saved using ObjectOutputStream.
  • When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.

transcient還有效果嗎

對于externalizable實(shí)現(xiàn)方式的代碼做如下修改:

// private Date logindate;
private transient Date logindate;

運(yùn)行結(jié)果與代碼改動之前一樣,說明transcient在externalizable實(shí)現(xiàn)的類中失效了

總結(jié)

  1. 兩個流:objectOutputStream酒来、ObjectInputStream

    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
    oos.writeObject(info);  
    oos.close(); 
    
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
    LoginInfo info2 = (LoginInfo)ois.readObject();
    ois.close();
    
  2. serialVersionUID的作用

    Java的序列化機(jī)制是通過在運(yùn)行時判斷類的serialVersionUID來驗(yàn)證版本一致性的卢未,所以實(shí)現(xiàn)Serializable或Externalizable接口都最好有serialVersionUID這一字段,沒有會報警告堰汉。

  3. Serializable與Externalizable的關(guān)系

    Serializable是個空接口辽社,用于指示某類的對象是否可以序列化。Externalizable接口繼承了Serializable接口翘鸭,添加了writeExternal滴铅、readExternal方法。

  4. transient關(guān)鍵字

    在Serializable接口實(shí)現(xiàn)的類中就乓,transient修飾的字段不參與序列化過程失息;在Externalizable接口實(shí)現(xiàn)的類中,transient無效档址。即:transient只能與Serializable搭配使用盹兢。

    public class LoginInfo implements Serializable{
     private static final long serialVersionUID = 8685376332791485990L;
     private String username;
     private transient String password; //不參與序列化過程
         ......
    }
    
  5. writeExternal、readExternal 與無參構(gòu)造函數(shù)

    Externalizable實(shí)現(xiàn)的類對象守伸,序列化與反序列化由writeExternal與readExternal兩個函數(shù)控制(內(nèi)部操作的字段要對應(yīng))绎秒,而且反序列時會先調(diào)用無參構(gòu)造函數(shù)創(chuàng)建實(shí)例對象,再通過readExternal讀取數(shù)據(jù)尼摹。所以Externalizable實(shí)現(xiàn)的類若想反序列化见芹,必須有無參構(gòu)造函數(shù)剂娄。而恢復(fù)Serializable對象,完全以之前存儲的二進(jìn)制為基礎(chǔ)來構(gòu)造玄呛,不調(diào)用構(gòu)造函數(shù)阅懦。

參考

  1. Java序列化與反序列化
  2. Java序列化—transient關(guān)鍵字和Externalizable接口
  3. Java序列化的幾種方式
  4. serialVersionUID的作用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市徘铝,隨后出現(xiàn)的幾起案子耳胎,更是在濱河造成了極大的恐慌,老刑警劉巖惕它,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怕午,死亡現(xiàn)場離奇詭異,居然都是意外死亡淹魄,警方通過查閱死者的電腦和手機(jī)郁惜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甲锡,“玉大人兆蕉,你說我怎么就攤上這事$吐伲” “怎么了虎韵?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疚俱。 經(jīng)常有香客問我劝术,道長缩多,這世上最難降的妖魔是什么呆奕? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮衬吆,結(jié)果婚禮上梁钾,老公的妹妹穿的比我還像新娘。我一直安慰自己逊抡,他們只是感情好姆泻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冒嫡,像睡著了一般拇勃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孝凌,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天方咆,我揣著相機(jī)與錄音,去河邊找鬼蟀架。 笑死瓣赂,一個胖子當(dāng)著我的面吹牛榆骚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播煌集,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼妓肢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苫纤?” 一聲冷哼從身側(cè)響起碉钠,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎方面,沒想到半個月后放钦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恭金,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年操禀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片横腿。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡颓屑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耿焊,到底是詐尸還是另有隱情揪惦,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布罗侯,位于F島的核電站器腋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钩杰。R本人自食惡果不足惜纫塌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讲弄。 院中可真熱鬧措左,春花似錦、人聲如沸避除。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓶摆。三九已至凉逛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間群井,已是汗流浹背状飞。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昔瞧。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓指蚁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親自晰。 傳聞我的和親對象是個殘疾皇子凝化,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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

  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程酬荞;反序列化de...
    步積閱讀 1,437評論 0 10
  • 簡介 對于一個存在于Java虛擬機(jī)中的對象來說搓劫,其內(nèi)部的狀態(tài)只保持在內(nèi)存中。JVM停止之后混巧,這些狀態(tài)就丟失了枪向。在很...
    FX_SKY閱讀 787評論 0 0
  • 序列化和反序列化的概念 序列化:把java對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化,這些字節(jié)序列可以被保存在磁盤上...
    snoweek閱讀 696評論 0 3
  • JAVA序列化機(jī)制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,837評論 0 24
  • 序列化的意義 1.永久存儲某個jvm中運(yùn)行時的對象。2.對象可以網(wǎng)絡(luò)傳輸3.rmi調(diào)用都是以序列化的方式傳輸參數(shù) ...
    炫邁哥閱讀 646評論 0 0