首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

深入解析 Java 序列化機制與核心原理

     編輯:Java 程式設計入門

前言

Java 序列化是將物件轉換為位元組流的過程,反序列化則是將位元組流重建為物件。這項機制解決了分散式環境下物件在網路或裝置間傳輸的問題,是 Java 平台提供的標準化物件持久化與傳輸方案。

序列化的必要性

在 Java 中一切皆物件。當需要在分散式系統中跨 JVM 傳遞物件,或將物件狀態持久化至磁碟時,必須有一套統一的協議將物件轉換為可傳輸、可儲存的位元組序列。Java 序列化 API 正是為此而生。

如何實現物件序列化

1. 實作 Serializable 介面

欲序列化的類別必須實作 java.io.Serializable 介面。此介面不包含任何方法,僅作為標記介面,告知 JVM 該類別支援序列化。

import java.io.Serializable;

class TestSerial implements Serializable {
    public byte version = 100;
    public byte count = 0;
}

2. 使用 ObjectOutputStream 輸出物件

ObjectOutputStream 可將物件寫入位元組流。以下範例將物件序列化並存入檔案:

public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("temp.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    TestSerial ts = new TestSerial();
    oos.writeObject(ts);
    oos.flush();
    oos.close();
}

3. 使用 ObjectInputStream 還原物件

反序列化讀取位元組流並重建物件:

public static void main(String[] args) throws IOException, ClassNotFoundException {
    FileInputStream fis = new FileInputStream("temp.out");
    ObjectInputStream oin = new ObjectInputStream(fis);
    TestSerial ts = (TestSerial) oin.readObject();
    System.out.println("version=" + ts.version);
}

執行結果輸出 100,證明物件成功還原。

序列化格式深度剖析

將序列化後的 temp.out 以十六進位檢視,內容如下:

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05
63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78
70 00 64

雖然 TestSerial 僅含兩個 byte 欄位(理論僅需 2 位元組),但實際檔案佔用 51 位元組。額外空間包含類別中繼資料、序列化版本 ID、欄位描述等結構性資訊。

Java 序列化演算法流程

  1. 輸出物件相關的類別中繼資料。
  2. 遞歸輸出父類別描述,直到無父類別為止。
  3. 類別中繼資料完成後,從最頂層父類別開始輸出實例實際資料值。
  4. 自上而下遞歸輸出各層級實例資料。

完整範例:繼承與組合情境

定義父類別 parent、組合類別 contain、主類別 SerialTest:

class parent implements Serializable {
    int parentVersion = 10;
}

class contain implements Serializable {
    int containVersion = 11;
}

public class SerialTest extends parent implements Serializable {
    int version = 66;
    contain con = new contain();
    // ... getter、main 方法省略
}

序列化後的位元組流(節錄關鍵片段):

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07
76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09
4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72
65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00
0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70
00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74
61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00
0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78
70 00 00 00 0B

位元組逐段解讀

1. 串流魔數與版本

  • AC ED:STREAM_MAGIC,宣告使用序列化協定。
  • 00 05:STREAM_VERSION,協定版本。
  • 73:TC_OBJECT,表示新物件開始。

2. SerialTest 類別描述 (TC_CLASSDESC)

  • 72:TC_CLASSDESC,新類別描述開始。
  • 00 0A:類別名稱長度 (10)。
  • 53 65 72 69 61 6C 54 65 73 74:類別名 "SerialTest"。
  • 05 52 81 5A AC 66 02 F6:serialVersionUID(未指定時由演算法產生)。
  • 02:旗標,表示支援序列化。
  • 00 02:欄位數量 (2)。

3. 欄位描述

int version:

  • 49:欄位型別代碼 'I' (int)。
  • 00 07:欄位名長度。
  • 76 65 72 73 69 6F 6E:"version"。

contain con(物件參考):

  • 4C:欄位型別代碼 'L' (物件參考)。
  • 00 03:欄位名長度。
  • 63 6F 6E:"con"。
  • 74:TC_STRING,使用字串引用類別簽名。
  • 00 09:字串長度。
  • 4C 63 6F 6E 74 61 69 6E 3B:JVM 簽名 "Lcontain;"。
  • 78:TC_ENDBLOCKDATA,物件資料塊結束。

4. 父類別 parent 描述

  • 72:TC_CLASSDESC。
  • 00 06:名稱長度。
  • 70 61 72 65 6E 74:"parent"。
  • 0E DB D2 BD 85 EE 63 7A:serialVersionUID。
  • 02:旗標。
  • 00 01:欄位數 (1)。
  • 欄位 parentVersion 描述 (型別 'I',名稱 "parentVersion")。
  • 78:TC_ENDBLOCKDATA。
  • 70:TC_NULL,無更上層父類別。

5. 實例資料輸出(自頂層父類別向下)

  • 00 00 00 0A:parentVersion = 10。
  • 00 00 00 42:version = 66。

6. 組合物件 contain 描述與資料

因先前未描述 contain 類別,此處補充:

  • 73:TC_OBJECT,新物件。
  • 72:TC_CLASSDESC,類別描述。
  • 類別名 "contain"、serialVersionUID、旗標、欄位數 (1)。
  • 欄位 containVersion 描述。
  • 78:TC_ENDBLOCKDATA。
  • 70:TC_NULL,無父類別。
  • 00 00 00 0B:containVersion = 11。

總結

Java 序列化演算法採用深度優先策略,先完整輸出類別階層的中繼資料(含版本 ID、欄位定義),再由根父類別向下輸出實例實際數值。這種設計確保反序列化時能精確重建物件圖,但也導致序列化後體積較大、效能較低。實務上若需高效能或跨語言互通,可考慮 Protobuf、Avro、JSON 等替代方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved