Java--對象的序列化和反序列化

對象序列化的目標:

  • 將對的字節序列對象永久的保存到磁盤中。
  • 允許在網絡上直接傳輸對象,傳輸對象的字節序列。

對象序列化:把對象轉換為字節序列。

對象反序列化:把字節序列恢復為對象。


如果讓某個對象支持序列化機制,必須讓它的類是可序列化的(serializable)。
為了讓某個類是可序列化的,該類必須實現如下兩個接口之一:

  • Serializable
  • Externalizablie

Externalizable接口繼承自 Serializable接口,僅實現Serializable接口的類采用默認的序列化方式 ,而實現Externalizable接口的類完全由自身來控制序列化的行為。

Java 的很多類已經實現了Serializable,該接口是一個標記接口,實現該接口無須實現任何方法,它只是表明該類的實例是可序列化的。

在 JavaEE 中,通常建議:創建的每個JavaBean類都實現Serializable

關于對象序列化,需要注意的幾點:

  • 對象的類名、實例變量都會被序列化;方法、類變量(static修飾的成員變量)、transient 修飾的實例變量都不會被序列化。
  • 實現 Serializable 接口的類如果需要讓某個實例變量不被實例化,則在該實例變量前加 transient 修飾符。
  • 要保證序列化對象的引用變量類型也是可序列化的。
  • 反序列化對象時必須有序列化對象的 class 文件。
  • 當通過文件、網絡來讀取序列化后的對象時,必須按實際寫入的順序讀取。

1、使用對象流來實現序列化

對象序列化的步驟:

1) 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流;

2) 通過對象輸出流的writeObject()方法寫對象。

對象反序列化的步驟:

1) 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流;

2) 通過對象輸入流的readObject()方法讀取對象。

public class Person implements Serializable {
    
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
    
}
/**
 * 實現序列化和反序列化
 *
 */
public class SerializableTest {

    public static void main(String[] args) {
        Person person = new Person("小明", 23);
        try {
            SerializableObject(person);
            Person p = (Person) DeserializationObject();
            System.out.println(p.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
                
    }
    
    /**
     * 序列化對象
     * @param o
     * @throws Exception
     */
    public static void SerializableObject(Object o) throws Exception {
        
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
        oos.writeObject(o);
        System.out.println("對象序列化成功!");
        oos.close();
        
    }
    
    /**
     * 反序列化
     * @return
     * @throws Exception
     */
    public static Object DeserializationObject() throws Exception {
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
        Object o = ois.readObject();
        System.out.println("反序列化成功!");
        return o;
        
    }
    
}

OK!

2、對象引用的序列化

前邊例子的Person類的成員變量是Stringint型的。

如果某個類的成員變量的類型不是基本類型或String類型,而是另一個引用類型,那么這個引用類必須是可序列化的,否則擁有該類型成員變量的類也是不可序列化的。

public class Teacher implements Serializable {

    private String name;
    private Person stuName;
    
}

Teacher類持有一個Person類的引用,只有Person類是可序列化的,Teacher類才是可序列化的。如果Person類不可序列化,則無論Teacher類是否實現了Serializable,Externalizable接口,Teacher類都是不可序列化的。

Java序列化機制采用了一種特殊的序列化算法:

  • 所有保存到磁盤中的對象都有一個序列化編號。
  • 當試圖序列化一個對象時,將先檢查該對象是否已經被序列化過,只有該對象從未(在本次虛擬機中)被序列化過,系統才會將該對象轉換成字節序列并輸出。
  • 如果某個對象已經被序列化過,程序將只輸出一個序列化編號,而不是再次序列化該對象。

注意:當使用Java序列化機制序列化可變對象時一定要注意,只有第一次調用 writeObject() 方法來輸出對象時才會將對象轉換成字節序列,并寫入到 ObjectOutputStream;在后面程序中即使該對象的實例變量發生了改變,再次調用writeObject() 方法輸出該對象時,改變后的實例變量也不會被輸出。

3、自定義序列化

有的時候,不希望系統將某一實例變量值進行序列化,或者某個實例變量的類型是不可序列化的。因此就不希望對該實例變量進行遞歸的序列化,以免引發NotSerializableException 異常。

對某個對象進行序列化時,系統會自動把該對象的所有實例變量依次進行序列化,如果某個實例變量引用到另一個對象,則被引用的對象也會被序列化。如果被引用的對象的實例變量也引用了其他對象,則被引用的對象也會被序列化,這種情況被稱為遞歸序列化

解決辦法一
在實例變量前面使用 transient 關鍵字修飾,可以指定Java序列化時無須理會該實例變量。

public class Teacher implements Serializable {

    private transient String name;
    private Person stuName;
    
}

transient 關鍵字只能用于修飾實例變量,不可修飾Java程序中的其他部分。

存在問題:被 transient 修飾的實例變量被完全隔離在序列化機制之外,將導致在反序列化時無法取得該實例變量的值。


解決辦法二
利用Java提供的一種序列化機制,通過這種機制可以讓程序控制如何序列化各實例變量,甚至完全不序列化某些實例變量(與使用 transient 關鍵字的效果相同)。

在序列化和反序列化過程中,需要特殊處理的類應該重寫如下的特殊方法,來實現自定義序列化:

private void writeObject(ObjectOutputStream out) throws IOException  // 可以自定義序列化機制

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException       // 可以自定義反序列化機制

private void readObjectNoData() throws ObjectStreamException    // 當序列化流不完整時,該方法可以用來正確地初始化反序列化的對象

注意:writeObject() 方法存儲實例變量的順序應該和readObject() 方法中恢復實例變量的順序一致,否則將不能正常恢復該Java對象。

解決辦法三
一種更徹底的自定義機制,可以在序列化對象時將該對象替換成其他對象。讓序列化類重寫以下方法:

任何的訪問修飾符  Object writeReplace() throws ObjectStreamException
public class Teacher implements Serializable {

    private String name;
    private Person stuName;
    
    // 重寫 writeReplace 方法,程序在序列化該對象之前,先調用該方法
    private Object writeReplace() throws ObjectStreamException {
        ArrayList<Object> list = new ArrayList<>();
        list.add(name);
        list.add(stuName);
        return list;
    }
}

與之相對應的一個方法:

任何的訪問修飾符  Object readResolve() throws ObjectStreamException

該方法在緊接著readObject() 之后被調用,該方法的返回值將會代替原來反序列化的對象,而原來readObject()反序列化的對象將會被立即丟棄。

通常建議:對于 final 類重寫 readResolve() 方法不會有任何問題,否則,重寫 readResolve() 方法時應該盡量使用 private 修飾符。

反序列化機制在恢復Java對象時無須調用構造器來初始化Java對象,從這個意義上來看,序列化機制可以用來“克隆”對象。

4、另一種自定義序列化機制

這種序列化方式完全由程序員決定存儲和恢復對象數據。實現Externalizablie接口,中的兩個方法:

void writeExternal(ObjectOutput out)  // 實現序列化,調用ObjectOutput的writeObject()方法來保存引用類型的實例變量的值。
調用DataOutput(它是ObjectOutput的父接口)的方法來保存基本類型的實例變量的值。
        
void readExternal(ObjectInput in)  // 實現反序列化,調用ObjectIutput的readObject()方法來恢復引用類型的實例變量的值。
調用DataIutput(它是ObjectIutput的父接口)的方法來恢復基本類型的實例變量的值。
public class Person implements Serializable {
    
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void writeExternal(ObjectOutput out) throws IOException {
        //將name實例變量值反轉后寫入
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
        
    }
    
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        this.name = ((StringBuffer) in.readObject()).reverse().toString();
        this.age = in.readInt(); 
    }
}

由于編程復雜度的原因,大部分時候都是采用實現Serializable接口的方式來實現序列化

6、版本(serialVersionUID 的作用)

Java序列化機制允許為序列化類提供一個private static final 的 serialVersionUID 值,該類變量的值用于標識該Java類的序列化版本。

這樣就能保證即使在某個對象被序列化之后,它所對應的類被修改了,該對象也依然可以被正確的反序列化。避免在反序列化時與序列化版本的兼容性問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,897評論 0 24
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化,必須實現Serializable接口。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,423評論 1 3
  • 如風又如煙 該從哪里說起呢?一提筆眼淚就控制不住泛濫成河,時過境遷你我漸行漸遠,這一場夢早就結束!很久之后,我恍然...
    洛子兮閱讀 199評論 0 0
  • ???1、幻想——思想的巨人,行動的矮子。 當人遇到挫折或難以解決的問題時,便脫離實際,想入非非,把自己放到想像的...
    李大女兒閱讀 266評論 0 0
  • 讀書到底是否有用?我這里的讀書不僅僅指閱讀,而是在學校接受教育。 經常會聽到這樣的言論:讀大學有什么用。現在這么多...
    咸魚不咸閱讀 321評論 0 0