Java 序列化是將物件轉為位元組流以便網路傳輸或持久化的標準機制。雖然日常開發只需實作 Serializable,但理解其底層字節結構與演算法流程,對排查序列化相容性問題、效能優化、安全防禦至關重要。本文透過實例逐字節解析序列化輸出。
// 1. 實作標記介面
class TestSerial implements Serializable {
public byte version = 100;
public byte count = 0;
}
// 2. 序列化寫入檔案
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("temp.out"));
oos.writeObject(new TestSerial());
oos.close();
// 3. 反序列化讀取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("temp.out"));
TestSerial ts = (TestSerial) ois.readObject();
System.out.println(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
| 位元組 | 含義 | 說明 |
|---|---|---|
| AC ED | STREAM_MAGIC | 魔數,識別序列化協議 |
| 00 05 | STREAM_VERSION | 序列化版本 |
| 73 | TC_OBJECT | 新物件開始 |
| 72 | TC_CLASSDESC | 類描述開始 |
| 00 0A | 類名長度 | 10 字元 |
| 53 65 72 69 61 6C 54 65 73 74 | 類名 | "SerialTest" UTF-8 |
| A0 0C 34 00 FE B1 DD F9 | serialVersionUID | 8 位元組,未指定則自動生成 |
| 02 | 旗標 | 02 = 支援序列化、非 SC_WRITE_METHOD |
| 00 02 | 欄位數 | 2 個欄位 |
| 42 00 05 63 6F 75 6E 74 | 欄位 1 | B=byte, 名稱 "count" |
| 42 00 07 76 65 72 73 69 6F 6E | 欄位 2 | B=byte, 名稱 "version" |
| 78 | TC_ENDBLOCKDATA | 類描述結束 |
| 70 | TC_NULL | 無父類 |
| 00 64 | 實例資料 | version=100(0x64), count=0(0x00) |
java.lang.Object。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();
// ... main 序列化
}
輸出字節流會依序包含:
private static final long serialVersionUID = 1L;,避免跨 JVM 版本或編譯器差異導致反序列化失敗。writeObject/readObject 可控制細節(如加密、壓縮、相容舊版)。ObjectInputFilter (Java 9+) 或第三方庫(如 NotSoSerial)防護。Java 序列化雖然自動化程度高,但其字節格式嚴格遵循規範。掌握「類描述遞迴、實例資料自頂向下、引用句柄去重」三大核心邏輯,能幫助你快速定位 InvalidClassException、StreamCorruptedException 等疑難雜症,也能在需要高效能、跨語言場景下果斷選擇 Protobuf、Avro 等替代方案。