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

深度解析 Java transient 關鍵字與序列化機制:原理、場景與 HashMap 實戰案例

     編輯:Java 綜合教學

前言

在 Java 序列化體系中,transient 是一個關鍵但常被誤解的修飾符。本文將從語法作用、適用場景、底層原理及 JDK 原始碼實戰(以 HashMap 為例)四個維度,徹底講透這個知識點。

transient 的核心作用

定義: 修飾實例欄位,使其從類的預設序列化形式中排除。即使用 ObjectOutputStream.defaultWriteObject() / defaultReadObject() 時,該欄位不會被自動寫入/讀取。

關鍵點: 它不代表「不序列化」,而是「不使用預設序列化機制」。開發者仍可通過自定義 writeObject/readObject 實現該欄位的序列化邏輯。

兩大典型應用場景

場景一:敏感資料不落地(業務安全需求)

如銀行密碼、Token、信用卡號等敏感資訊,不希望出現在序列化後的位元組流中(防止被反序列化還原或日誌洩露)。

public class UserAccount implements Serializable {
    private String username;
    // 密碼不參與預設序列化
    private transient String password; 
    
    // 若業務確需持久化,可在 writeObject 中加密寫入
}
註:此用法常見於教材,但實務上更推薦使用專門的加密序列化框架或避免敏感資料進入領域模型。

場景二:複雜資料結構需自定義序列化策略(效能與正確性)

這是 JDK 集合框架大量採用的模式。以 HashMap 為例,其核心儲存結構 table(陣列+鏈表/紅黑樹)被標記為 transient:

// HashMap.java 片段
transient Node<K,V>[] table;

為什麼不直接序列化 table?

  1. 結構冗餘: 預設序列化會遞迴遍歷整個對象圖,包含所有 Entry 節點、鏈表指標、樹結構等,產生大量冗餘位元組。
  2. 效能災難: 大 Map 序列化耗時極長,且極易觸發 StackOverflowError(遞迴深度過大)。
  3. 語義不對: 我們關心的是「鍵值對映射關係」,而非具體的桶位索引、鏈表順序等實現細節。反序列化時重新 hash(key) 計算索引更合理。

HashMap 的自定義序列化實現

寫入: 僅序列化 size、loadFactor 等元數據,然後遍歷 entrySet() 依次寫入 Key-Value。

private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject(); // 寫入非 transient 欄位
    s.writeInt(size);       // 額外寫入 size
    for (Map.Entry<K,V> e : entrySet()) {
        s.writeObject(e.getKey());
        s.writeObject(e.getValue());
    }
}

讀取: 讀取元數據後,依次讀取 Key-Value 並調用 putForCreate 重建表結構。

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    s.readInt(); // 讀取 size
    for (int i = 0; i < size; i++) {
        K key = (K) s.readObject();
        V value = (V) s.readObject();
        putForCreate(key, value); // 重新計算 hash、放入桶位
    }
}

最佳實踐總結

場景處理方式關鍵點
敏感欄位transient + 不自定義序列化完全排除,反序列化後為 null/default
衍生/可計算欄位transient + readObject 中重算如緩存值、哈希碼、聚合統計值
複雜結構transient + 完整自定義 writeObject/readObject只序列化邏輯數據,重建時按業務邏輯組裝
非 Serializable 物件引用transient + 代理模式/外部化如資料庫連接、執行緒池、原生資源句柄

常見誤區澄清

  • 誤區: transient 修飾的欄位反序列化後一定是 null。
    真相: 若類定義了 readObject 並手動賦值,則為手動賦的值;否則才是預設值。
  • 誤區: static 欄位需要 transient 才不序列化。
    真相: static 欄位屬於類不屬於實例,預設就不參與序列化,加不加 transient 效果相同。
  • 誤區: 加了 transient 就無法序列化該欄位。
    真相: 只要在 writeObject 中顯式 s.writeObject(field) 即可序列化。

結語

transient 的本質是「交還序列化控制權給開發者」。理解它與 Serializable、Externalizable、writeReplace/readResolve 的協作關係,是掌握 Java 序列化進階能力的關鍵。下次看到 HashMap、ArrayList、ThreadPoolExecutor 等核心類中大量 transient 欄位時,你就知道:它們正在用最高效的方式保護你的數據完整性與效能。

  1. 上一篇文章:
  2. 下一篇文章:
JS 載入時機陷阱:為什麼  script  放在 <head> 會找不到 DOM 元素? JS 載入時機陷阱:為什麼