Java:序列化和反序列化

1.背景

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

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

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

2.序列化協(xié)議+序列化和反序列化

  • 序列化是指:將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成特定的格式,使其可以在網(wǎng)絡(luò)中傳輸,或可存儲在內(nèi)存/文件中。

序列化后的數(shù)據(jù)必須是可保持或可傳輸?shù)臄?shù)據(jù)格式,例如:二進(jìn)制串/字節(jié)流、XML、JSON等。

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

自問自答

  • 問:序列化的目的是什么?
  • 答:方便的進(jìn)行數(shù)據(jù)的交換和傳輸工作。

3.JDK類庫中的序列化API

Java本身提供了對數(shù)據(jù)/對象序列化的支持。

  • 輸入輸出流

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

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

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

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

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

運行結(jié)果:

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

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

3.2 隱藏指定字段

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

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

運行結(jié)果:

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


運行結(jié)果:

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

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

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

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

3.4 實現(xiàn) Externalizable 接口

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

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

運行結(jié)果:

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

這里有幾個關(guān)鍵單需要說明:

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

4.關(guān)于 Serializable 和 Externalizable 的總結(jié)和附加說明

  • 構(gòu)造器:

    • Serializable 序列化時不會調(diào)用默認(rèn)的構(gòu)造器;
    • Externalizable 序列化時會調(diào)用默認(rèn)構(gòu)造器。
  • 功能

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

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

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

自問自答

  • 問: writeObject()readObject() 都是private方法,它們是怎么被調(diào)用的呢?
  • 答:很顯然,反射。詳情可見 ObjectOutputStream 中的 writeSerialData 方法,以及 ObjectInputStream 中的 readSerialData 方法。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容