亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Java 序列化與反序列化

上一小節我們學習了 Java 的輸入輸出流,有了這些前置知識點,我們就可以學習 Java 的序列化了。本小節將介紹什么是序列化、什么是反序列化、序列化有什么作用,如何實現序列化與反序列化,Serializable 接口介紹,常用序列化工具介紹等內容。了解序列化的用途、學會如何進行序列化和反序列化操作是本小節的重點內容。

1. 序列化與反序列化

序列化在計算機科學的數據處理中,是指將數據結構或對象狀態轉換成可取用格式,以留待后續在相同或另一臺計算機環境中,能恢復原先狀態的過程。依照序列化格式重新獲取字節的結果時,可以利用它來產生與原始對象相同語義的副本。

很多編程語言自身就支持序列化操作。Java 語言提供自動序列化,序列化(serialize)就是將對象轉換為字節流;與之相應對的,反序列化(deserialize)就是將字節流轉換為對象。

需要注意的是,Java 序列化對象時,會把對象的狀態保存成字節序列,對象的狀態指的就是其成員變量,因此序列化的對象不會保存類的靜態變量。

在 Java 中,可通過對象輸出/輸入流來實現序列化/反序列化操作。 java.io包中,提供了ObjectInputStream類和ObjectOutputStream用來序列化對象,這兩個類我們將在下面介紹。下面我們來介紹一下序列化的作用。

2. 序列化的作用

  • 序列化可以將對象的字節序列存持久化:可以將其保存在內存、文件、數據庫中(見下圖);
  • 可以在網絡上傳輸對象字節序列;
  • 可用于遠端程序方法調用。

3. 實現序列化

  • ObjectOutputStream類下的void writeObject(Object obj)方法用于將一個對象寫入對象輸出流,也就是序列化;
  • ObjectInputStream類下的Object readObject()方法用于讀取一個對象到輸入流,也就是反序列化。

實例代碼如下:

import java.io.*;

public class SerializeDemo1 {

    static class Cat implements Serializable {
        private static final long serialVersionUID = 1L;

        private String nickname;

        private Integer age;

        public Cat() {}

        public Cat(String nickname, Integer age) {
            this.nickname = nickname;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Cat{" +
                    "nickname='" + nickname + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    /**
     * 序列化方法
     * @param filepath 文件路徑
     * @param cat 要序列化的對象
     * @throws IOException
     */
    private static void serialize(String filepath, Cat cat) throws IOException {
        // 實例化file對象
        File file = new File(filepath);
        // 實例化文件輸出流
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        // 實例化對象輸出流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        // 保存cat對象
        objectOutputStream.writeObject(cat);
        // 關閉流
        fileOutputStream.close();
        objectOutputStream.close();
    }

    /**
     * 反序列化方法
     * @param filepath 文件路徑
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static void deserialize(String filepath) throws IOException, ClassNotFoundException {
        // 實例化file對象
        File file = new File(filepath);
        // 實例化文件輸入流
        FileInputStream fileInputStream = new FileInputStream(file);
        // 實例化對象輸入流
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Object o = objectInputStream.readObject();
        System.out.println(o);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String filename = "C:\\Users\\Colorful\\Desktop\\imooc\\Hello.txt";
        Cat cat = new Cat("豬皮", 1);
        serialize(filename, cat);
        deserialize(filename);
    }

}

運行結果:

Cat{nickname='豬皮', age=1}

上述代碼中,我們定義了一個Cat類,它實現了Serializable接口,類內部有一個private static final long serialVersionUID = 1L;,關于這兩點,我們下面緊接著就會介紹。

除了Cat類的定義,我們還分別封裝了序列化與反序列化的方法,并在主方法中調用了這兩個方法,實現了cat對象的序列化和反序列化操作。

在調用序列化方法后,你會發現磁盤中的Hello.txt文件中被cat對象寫入了序列化后的數據:

4. Seralizable 接口

被序列化的類必須是Enum、ArraySerializable中的任意一種類型。

如果要序列化的類不是枚舉類型和數組類型的話,則必須實現java.io.Seralizable接口,否則直接序列化將拋出NotSerializableException異常。

4.1 serialVersionUID

serialVersionUID 是 Java 為每個序列化類產生的版本標識。它可以用來保證在反序列化時,發送方發送的和接受方接收的是可兼容的對象。如果接收方接收的類的 serialVersionUID 與發送方發送的 serialVersionUID 不一致,會拋出 InvalidClassException

4.2 默認序列化機制

如果僅僅只是讓某個類實現 Serializable 接口,而沒有其它任何處理的話,那么就會使用默認序列化機制。

使用默認機制,在序列化對象時,不僅會序列化當前對象本身,還會對其父類的字段以及該對象引用的其它對象也進行序列化。同樣地,這些其它對象引用的另外對象也將被序列化,以此類推。所以,如果一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那么這個序列化的過程就會較復雜,開銷也較大。

4.3 transient 關鍵字

在現實應用中,有些時候不能使用默認序列化機制。比如,希望在序列化過程中忽略掉敏感數據,或者簡化序列化過程。下面將介紹若干影響序列化的方法。

當某個字段被聲明為 transient 后,默認序列化機制就會忽略該字段。

可以嘗試將實例代碼中Cat類的成員變量age聲明為transient

// 僅部分代碼
static class Cat implements Serializable {
    transient private Integer age;
}

運行程序,我們會發現成員變量age沒有被序列化。

5. 常用序列化工具

Java 官方的序列化存在很多缺點,因此,開發者們更傾向于使用優秀的第三方序列化工具來替代 Java 自身的序列化機制。

Java 官方的序列化主要體現在以下方面:

  • 性能問題:序列化后的數據相對于一些優秀的序列化的工具,還是要大不少,這大大影響存儲和傳輸的效率;
  • 繁瑣的步驟:Java 官方的序列化一定需要實現 Serializable 接口,略顯繁瑣,而且需要關注 serialVersionUID;
  • 無法跨語言使用:序列化的很大一個目的就是用于不同語言來讀寫數據。

下面列舉了一些優秀的序列化工具:

  • thriftprotobuf - 適用于對性能敏感,對開發體驗要求不高的內部系統。
  • hessian - 適用于對開發體驗敏感,性能有要求的內外部系統。
  • jacksongson、fastjson - 適用于對序列化后的數據要求有良好的可讀性(轉為 json 、xml 形式)。

6. 小結

通過本小節的學習,我們知道了序列化(serialize)就是將對象轉換為字節流,反序列化(deserialize)就是將字節流轉換為對象。想要實現序列化,就必須繼承Seralizable接口,serialVersionUID 是 Java 為每個序列化類產生的版本標識。當某個字段被聲明為 transient 后,默認序列化機制就會忽略該字段。學會根據自己的應用場景選擇使用序列化工具。