Java:序列化和反序列化

1.背景

某天,我在寫代碼定義 bean 的時候,順手寫了個 public class User implements Serializable,旁邊的小哥哥看到了問我:你為什么要實現 Serializable 接口?你哪里用到它了嗎?不實現這個接口可以嗎?

emmm,皺眉沉思一下,好像也可以?

好吧,那先來了解一下 Serializable 接口涉及到的相關概念。

2.序列化協議+序列化和反序列化

  • 序列化是指:將數據結構或對象轉換成特定的格式,使其可以在網絡中傳輸,或可存儲在內存/文件中。

序列化后的數據必須是可保持或可傳輸的數據格式,例如:二進制串/字節流、XML、JSON等。

  • 反序列化:是序列化的逆過程,將對象從序列化數據中還原出來。

自問自答

  • 問:序列化的目的是什么?
  • 答:方便的進行數據的交換和傳輸工作。

3.JDK類庫中的序列化API

Java本身提供了對數據/對象序列化的支持。

  • 輸入輸出流

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

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

    • 創建一個對象輸出流。
    • 通過對象輸出流的 writeObject() 方法寫對象。
  • 對象反序列化步驟:

    • 創建一個對象輸入流。
    • 通過對象輸入流的 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="我是靜態變量,我不會被序列化";
    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("令習習習");
        user.setAddress("北京");

        System.out.println("對象:"+user.toString());
        System.out.println("對象中的靜態變量:"+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("我是靜態變量,我的值是存在JVM靜態存儲區的,不是反序列化來的");
        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("對象中的靜態變量:"+user.getHH());
        ois.close();
    }
}

運行結果:

對象:User{userId=1223, userName='令習習習', address='北京'}
對象中的靜態變量:我是靜態變量,我不會被序列化
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習習習', address='北京'}
對象中的靜態變量:我是靜態變量,我的值是存在JVM靜態存儲區的,不是反序列化來的
  • 這是一個簡單的序列化和反序列化例子,創建一個 User 實例,將其全部數據序列化到文件;然后再從文件讀取數據反序列化為對象。

  • 需要特別關注的是:對象序列化保存的是對象的"狀態",即它的成員變量。因此,對象序列化不會關注類中的靜態變量。

3.2 隱藏指定字段

在某些場景下,你希望某些字段不要被序列化,此時可以使用 transient 關鍵字來進行排除。

  • transient 關鍵字只修飾變量,不修飾方法和類。
  • transient 關鍵字修飾的變量不再能被序列化,自然也不會被反序列化回來。
// 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("令習習習");
        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();
    }
}

運行結果:

對象:User{userId=1223, userName='令習習習', address='北京'}
序列化成功
反序列化成功
對象:User{userId=1223, userName='令習習習', 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("令習習習");
        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();
    }
}


運行結果:

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

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

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

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

3.4 實現 Externalizable 接口

在Java中,對象的序列化可以通過實現兩種接口來實現:

  • 若實現的是 Serializable 接口,則所有的序列化將會自動進行,如果你希望在此基礎之上加點自定義的內容,就可以像上面那樣加兩個方法就ok了。
  • 若實現的是 Externalizable 接口,則沒有任何東西可以自動序列化,需要在
    writeExternal() 方法中進行手工指定所要序列化的變量,以及如何序列化,這與是否被 transient 修飾無關(也就是說,當你不需要java自動為你序列化的時候,transient就失效了);當然 readExternal() 也需要做相應的處理。
// 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("令習習習");
        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();
    }
}

運行結果:

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

這里有幾個關鍵單需要說明:

  • 實現 Externalizable 接口,一定要自定義序列化方法,如果你把 writeExternal()readExternal() 這里面的實現都丟掉,就會發現,java真的什么都不會做。
  • 如上面所說,當實現了 Externalizable 接口的時候,transient 關鍵字不再生效。
  • 反序列化時,實際上調用了 User 的無參構造函數,因此在自定義序列化方案的時候,請一定要記得提供一個 公共無參構造函數 ,不然就悲劇了。

4.關于 Serializable 和 Externalizable 的總結和附加說明

  • 構造器:

    • Serializable 序列化時不會調用默認的構造器;
    • Externalizable 序列化時會調用默認構造器。
  • 功能

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

    • 由于 Externalizable 對象默認不保存對象的任何字段,所以 transient 關鍵字只能伴隨 Serializable 使用,雖然 Externalizable 對象中使用 transient 關鍵字也不報錯,但不起任何作用。
  • 方法

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

自問自答

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

推薦閱讀更多精彩內容

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