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

Java 集合框架深度剖析:ArrayList 源碼級解讀與擴容機制詳解

     編輯:Java 綜合教學

Java 集合框架深度剖析:ArrayList 源碼級解讀與擴容機制詳解

1. ArrayList 核心定位與特性

ArrayList 實現 List 介面,底層由動態陣列 (Object[]) 支撐。核心特性:

  • 隨機訪問極快:get(i)/set(i,e) 為 O(1)。
  • 尾部新增均攤 O(1):得益於擴容策略,add(e) 平均常數時間。
  • 中間插入/刪除 O(n):需陣列元素大量搬移 (System.arraycopy)。
  • 非執行緒安全:多執行緒環境需外部同步或用 Collections.synchronizedList / CopyOnWriteArrayList。
  • Fail-Fast 迭代器:結構性修改時拋 ConcurrentModificationException,僅用於檢測 Bug,不可依賴於業務邏輯。

2. 核心屬性解讀 (JDK 8 為例)

private static final int DEFAULT_CAPACITY = 10;      // 默認初始容量
private static final Object[] EMPTY_ELEMENTDATA = {}; // 空陣列實例 (延遲初始化用)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // JDK 8+ 区分显式0与默认
transient Object[] elementData; // 核心存儲陣列,transient 避免默認序列化 (自定義 writeObject/readObject)
private int size;               // 實際元素個數 (非陣列長度)

疑問解答:

  1. transient:elementData 可能未滿 (容量 > size),默認序列化會浪費空間且洩漏內部結構。ArrayList 自定義 writeObject 僅序列化 size 個有效元素。
  2. size vs length:陣列長度 = 容量 (capacity),通常 ≥ size。size 追蹤邏輯大小,支援 isEmpty()、邊界檢查等。

3. 三種構造器與初始化策略

// 1. 指定初始容量:避免擴容開銷,預知大概數據量時強烈建議使用
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) this.elementData = new Object[initialCapacity];
    else if (initialCapacity == 0) this.elementData = EMPTY_ELEMENTDATA;
    else throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}

// 2. 無參構造:延遲初始化,首次 add 時才分配 DEFAULT_CAPACITY (10)
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

// 3. 集合轉換:toArray() 可能返回非 Object[],需防禦性複製修正類型
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0 && elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

4. 核心擴容機制:grow() 與 ensureCapacityInternal()

public boolean add(E e) {
    ensureCapacityInternal(size + 1); // 確保容量
    elementData[size++] = e;          // 賦值並遞增 size
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 首次擴容至少 10
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 結構修改計數器,支援 fail-fast
    if (minCapacity - elementData.length > 0)
        grow(minCapacity); // 觸發擴容
}

// 核心擴容邏輯:新容量 = 舊容量 + 舊容量 >> 1 (即 1.5 倍)
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

關鍵點:1.5 倍增長 (JDK 6/7 為 1.5 倍 + 1,JDK 8+ 為 1.5 倍) 平衡空間浪費與擴容頻次;Arrays.copyOf 底層調用 System.arraycopy (native),極高效。

5. 實用輔助方法

  • ensureCapacity(int minCapacity):公開 API,批量添加前預分配,避免多次擴容複製。僅在元素已存在時有效 (空陣列時內部邏輯會調整為默認容量)。
  • trimToSize():將容量壓縮至 size,釋放未使用記憶體,適合「不再新增、長期持有」場景。

6. Fail-Fast 機制原理

迭代器持有 expectedModCount = modCount 快照。每次 next()、remove() 前檢查 modCount == expectedModCount,不等則拋 ConcurrentModificationException。注意:非同步並發修改下無法保證 100% 檢測到,僅作調試輔助。

總結:ArrayList 使用建議

  1. 預估數據量 → new ArrayList<>(estimatedSize)。
  2. 頻繁中間增刪 → 改用 LinkedList (但隨機訪問極慢) 或業務重構。
  3. 多執行緒寫入 → CopyOnWriteArrayList (讀多寫少) 或外部鎖 / 並發容器。
  4. 需序列化/傳輸 → 放心用,自定義序列化高效安全。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved