Java 序列化是將物件轉換為位元組流的過程,反序列化則是將位元組流重建為物件。這項機制解決了分散式環境下物件在網路或裝置間傳輸的問題,是 Java 平台提供的標準化物件持久化與傳輸方案。
在 Java 中一切皆物件。當需要在分散式系統中跨 JVM 傳遞物件,或將物件狀態持久化至磁碟時,必須有一套統一的協議將物件轉換為可傳輸、可儲存的位元組序列。Java 序列化 API 正是為此而生。
欲序列化的類別必須實作 java.io.Serializable 介面。此介面不包含任何方法,僅作為標記介面,告知 JVM 該類別支援序列化。
import java.io.Serializable;
class TestSerial implements Serializable {
public byte version = 100;
public byte count = 0;
}
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();
}
反序列化讀取位元組流並重建物件:
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、欄位描述等結構性資訊。
定義父類別 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
AC ED:STREAM_MAGIC,宣告使用序列化協定。00 05:STREAM_VERSION,協定版本。73:TC_OBJECT,表示新物件開始。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)。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,物件資料塊結束。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,無更上層父類別。00 00 00 0A:parentVersion = 10。00 00 00 42:version = 66。因先前未描述 contain 類別,此處補充:
73:TC_OBJECT,新物件。72:TC_CLASSDESC,類別描述。serialVersionUID、旗標、欄位數 (1)。containVersion 描述。78:TC_ENDBLOCKDATA。70:TC_NULL,無父類別。00 00 00 0B:containVersion = 11。Java 序列化演算法採用深度優先策略,先完整輸出類別階層的中繼資料(含版本 ID、欄位定義),再由根父類別向下輸出實例實際數值。這種設計確保反序列化時能精確重建物件圖,但也導致序列化後體積較大、效能較低。實務上若需高效能或跨語言互通,可考慮 Protobuf、Avro、JSON 等替代方案。