java序列化與反序列化

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


定義

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

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

應(yīng)用場景

當(dāng)兩個(gè)進(jìn)程進(jìn)行遠(yuǎn)程通信時(shí),可以相互發(fā)送各種類型的數(shù)據(jù),包括文本、圖片、音頻、視頻等, 而這些數(shù)據(jù)都會以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。那么當(dāng)兩個(gè)Java進(jìn)程進(jìn)行通信時(shí),如何實(shí)現(xiàn)進(jìn)程間的對象傳送呢?這就需要Java序列化與反序列化了。一方面,發(fā)送方需要把這個(gè)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,這個(gè)使用的比較多。Serializable接口是一個(gè)空的接口,它的主要作用就是標(biāo)識這個(gè)類的對象是可序列化的。
  2. 實(shí)現(xiàn)接口Externalizable。Exterinable繼承了Serializable,是Serializable的一個(gè)擴(kuò)展,對于哪些屬性可以序列化,哪些可以反序列化可以做詳細(xì)地約束。

相關(guān)工具類:

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

    它是OutputStream類的一個(gè)子類,對應(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"); //某個(gè)待序列化對象,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é)序列,再把它們反序列化成為一個(gè)對象,并返回。

    使用方式如下:

    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)這個(gè)接口是空的!沒有字段,沒有方法,只是標(biāo)識一個(gè)類的對象是否可序列化(實(shí)現(xiàn)了這個(gè)接口,就表示可以序列化)。

serialVersionUID的作用

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

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

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

  1. 添加默認(rèn)值,即1L;
  2. 添加自動(dòng)產(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)字段的定義時(shí)使用關(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接口,并添加了兩個(gè)新的方法,分別表示在哪些字段能被序列化和哪些字段能夠反序列化。

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

erialVersionUID和之前一樣,最好添加。(但不添加的話,eclipse竟然連警告都沒有!!無奈,只能自己隨便寫個(gè)數(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)這種方式反序列化的時(shí)候竟然調(diào)用了無參構(gòu)造函數(shù)。對于恢復(fù)Serializable對象,完全以它存儲的二進(jìn)制為基礎(chǔ)來構(gòu)造,而不調(diào)用構(gòu)造函數(shù)。而對于一個(gè)Externalizable對象,public的無參構(gòu)造函數(shù)將會被調(diào)用。如果沒有public的無參構(gòu)造函數(shù),運(yùn)行時(shí)會報(bào)異常(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é)果與代碼改動(dòng)之前一樣,說明transcient在externalizable實(shí)現(xiàn)的類中失效了

總結(jié)

  1. 兩個(gè)流: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)行時(shí)判斷類的serialVersionUID來驗(yàn)證版本一致性的,所以實(shí)現(xiàn)Serializable或Externalizable接口都最好有serialVersionUID這一字段,沒有會報(bào)警告。

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

    Serializable是個(gè)空接口,用于指示某類的對象是否可以序列化。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兩個(gè)函數(shù)控制(內(nèi)部操作的字段要對應(yīng)),而且反序列時(shí)會先調(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,639評論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,626評論 2 370

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

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