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

Java 序列化與反序列化深度解析:從原理到 ArrayList 實戰

     編輯:Java 相關知識

什麼是序列化?

將物件狀態(非靜態、非 transient 欄位)轉為位元組流,以便持久化、網路傳輸、RMI 傳遞。反序列化為逆過程。

核心契約

  • 實作 java.io.Serializable 標記介面(無方法)。
  • 使用 ObjectOutputStream.writeObject() / ObjectInputStream.readObject()。
  • 靜態欄位、transient 欄位不參與序列化。
  • 父類也需實作 Serializable,否則父類欄位重置為預設值。

serialVersionUID 關鍵作用

private static final long serialVersionUID = 8683452581122892189L;
  • 反序列化時 JVM 比對 UID,不一致拋 InvalidClassException。
  • 未顯式宣告時 JVM 依類結構自動生成,結構變動導致 UID 變化,建議顯式指定。

自定義序列化策略:writeObject / readObject

若類中定義 private void writeObject(ObjectOutputStream) 與 private void readObject(ObjectInputStream),JVM 會透過反射優先調用,完全掌控二進制格式。

ArrayList 經典案例分析

源碼片段

public class ArrayList<E> ... implements Serializable {
    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // 底層陣列
    private int size;
    
    private void writeObject(ObjectOutputStream s) throws IOException {
        int expectedModCount = modCount;
        s.defaultWriteObject();           // 寫入非 transient 欄位
        s.writeInt(size);                 // 寫入容量(兼容 clone)
        for (int i = 0; i < size; i++)    // 僅寫入實際元素
            s.writeObject(elementData[i]);
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
    
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;
        s.defaultReadObject();            // 讀取 size 等
        s.readInt();                      // 讀取容量(忽略)
        if (size > 0) {
            ensureCapacityInternal(size);
            Object[] a = elementData;
            for (int i = 0; i < size; i++)
                a[i] = s.readObject();    // 逐個讀取元素
        }
    }
}

為何這樣設計?

  1. transient elementData:陣列容量通常大於實際元素數,直接序列化會寫入大量 null,浪費空間。
  2. 自定義 writeObject/readObject:僅序列化 size 個真實元素,極大壓縮體積。
  3. defaultWriteObject/ReadObject:委託 JVM 處理標準欄位(size、modCount 等),再手動處理陣列內容。

反射調用機制

// ObjectOutputStream 內部邏輯簡化
if (clazz.hasWriteObjectMethod()) {
    Method m = clazz.getDeclaredMethod("writeObject", ObjectOutputStream.class);
    m.setAccessible(true);
    m.invoke(obj, out); // 反射調用自定義方法
} else {
    defaultWriteObject(obj, out);
}

安全與最佳實踐

  1. 敏感欄位加密:writeObject 中加密密碼,readObject 解密(需金鑰管理)。
  2. 防範反序列化攻擊:
    • JDK 9+ 使用 ObjectInputFilter 白名單類別。
    • 避免反序列化不可信資料;採用 JSON/Protobuf 替代。
  3. 外部化配置:Externalizable 介面完全手動控制,但維護成本高,慎用。
  4. 版本演進:新增欄位預設值、保留 serialVersionUID、提供 readObjectNoData() 處理舊版本資料。

總結

  • Serializable 是標記,JVM 依此決定是否進入序列化流程。
  • transient 排除欄位;自定義 writeObject/readObject 實現精準控制。
  • ArrayList 透過 transient + 自定義方法實現高效序列化,是教科書級範例。
  • 生產環境優先考慮 JSON/Protobuf,僅在 RMI、分散式快取等場景使用 Java 原生序列化。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved