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

JDK 原始碼深度解析:ArrayList 核心機制與擴容策略全解析

     編輯:Java 綜合教學

JDK 原始碼深度解析:ArrayList 核心機制

ArrayList 是 Java 集合框架中使用頻率最高的動態陣列實現。本文基於 JDK 1.8+ 原始碼,深度剖析其核心屬性、擴容機制、核心方法實現與效能特徵。

一、核心屬性結構

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable {

    // 序列化版本號
    private static final long serialVersionUID = 8683452581122892189L;

    // 預設初始容量
    private static final int DEFAULT_CAPACITY = 10;

    // 空陣列實例(共享,節省記憶體)
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 底層儲存陣列(transient 不序列化,由 writeObject/readObject 自定義)
    transient Object[] elementData;

    // 實際元素數量
    private int size;

    // 修改次數(用於 fail-fast 機制)
    protected transient int modCount = 0;
}

關鍵設計點

  • Object[] elementData:底層儲存容器,容量 = 陣列長度
  • size:實際元素個數,永遠 ≤ capacity
  • 兩個空陣列常量:延遲初始化優化,無參建構子使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,指定容量 0 使用 EMPTY_ELEMENTDATA
  • modCount:實作 fail-fast 迭代器的關鍵

二、建構子與初始化策略

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

// 指定初始容量:避免多次擴容複製
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);
    }
}

// Collection 建構子
public ArrayList(Collection c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class) {
            elementData = Arrays.copyOf(elementData, size, Object[].class);
        }
    } else {
        elementData = EMPTY_ELEMENTDATA;
    }
}

三、擴容機制深度解析

1. 核心擴容流程

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 確保容量
    elementData[size++] = e;           // 新增元素
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity);
    }
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 1.5 倍擴容
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2. 擴容關鍵特性

特性說明
增長因子1.5 倍(oldCapacity + oldCapacity/2)
首次擴容無參建構子首次 add 時從 0 → 10
複製開銷Arrays.copyOf 為 native 方法,效能較好但仍需避免頻繁擴容
最大容量Integer.MAX_VALUE - 8(約 2GB 陣列限制)

四、核心方法原始碼分析

1. 隨機存取 get(int index) - O(1)

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}

2. 指定位置插入 add(int index, E element) - O(n)

public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

3. 批次新增 addAll(Collection c)

public boolean addAll(Collection c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

4. 刪除操作 remove(int index) / remove(Object o)

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    elementData[--size] = null;  // 釋放引用,協助 GC
    return oldValue;
}

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    elementData[--size] = null;
}

5. 清空 clear()

public void clear() {
    modCount++;
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}

五、進階特性與效能優化

1. Fail-Fast 迭代器機制

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

迭代器建立時記錄 expectedModCount = modCount,遍歷過程中若 modCount 變化則拋出異常。注意:這是檢測邏輯錯誤的輔助機制,非執行緒安全保證。

2. 縮容 trimToSize()

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
    }
}

大量刪除元素後調用可釋放未使用記憶體。

3. 排序 sort(Comparator c)

public void sort(Comparator c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    modCount++;
}

4. 淺複製 clone()

public Object clone() {
    try {
        ArrayList v = (ArrayList) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}

僅複製陣列結構,元素物件共享引用。深複製需自行實作。

六、效能總結與使用建議

操作時間複雜度說明
get/setO(1)隨機存取極快
add(尾部)O(1) 攤還偶爾觸發擴容 O(n)
add(指定位置)O(n)需移動後續元素
remove(指定位置)O(n)需移動後續元素
contains/indexOfO(n)線性搜尋
遍歷O(n)for/for-each/iterator 皆可

最佳實踐

  • 預估容量:new ArrayList<>(expectedSize) 減少擴容開銷
  • 批次操作:優先使用 addAll、removeAll 而非迴圈單個操作
  • 避免中間插刪:頻繁中間操作考慮 LinkedList
  • 並發場景:使用 CopyOnWriteArrayList 或 Collections.synchronizedList
  • 只讀場景:Collections.unmodifiableList(list) 或 Java 10+ List.copyOf(list)

ArrayList 以動態陣列為核心,提供優異的隨機存取效能與緊湊記憶體佈局。理解其擴容策略、Fail-Fast 機制與各操作複雜度,能幫助開發者在正確場景下發揮最大效能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved