下面開始對 Java 中 ArrayList
的深度源碼分析,基于 JDK 8
的實現(后續版本略有差異,但核心邏輯一致)。我們將從 類結構、擴容機制、核心方法實現、性能優化、線程安全問題 等角度進行詳細解析
一、類結構與核心字段
1. 類繼承關系
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
AbstractList
:提供了 List 接口的部分默認實現(如get(int index)
、size()
等)RandomAccess
:標記接口,表示支持隨機訪問(通過索引訪問元素,O(1)
時間復雜度)Cloneable
:支持克隆Serializable
:支持序列化
2. 核心字段
// 默認初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空數組常量(用于無參構造)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 空數組常量(用于指定容量為 0 的構造)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 存儲元素的數組
transient Object[] elementData;
// 當前元素個數
private int size;
// 數組最大容量限制(防止溢出)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
關鍵點
elementData
:實際存儲數據的數組,初始化為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
(空數組),首次添加元素時擴容到默認容量 10size
:記錄當前集合中實際元素的數量,與elementData.length
不同。transient
:elementData
被標記為transient
,但ArrayList
自定義了writeObject
和readObject
方法,實現序列化邏輯MAX_ARRAY_SIZE
:限制數組最大容量為Integer.MAX_VALUE - 8
,避免 JVM 內存分配異常
.
二、構造方法詳解
1. 無參構造函數
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 初始化為空數組
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 首次添加元素時,會擴容到默認容量 10
2. 帶初始容量的構造函數
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);}
}
- 直接初始化指定容量的數組,避免后續頻繁擴容
3. 使用集合初始化
public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {this.elementData = EMPTY_ELEMENTDATA;}
}
- 將集合轉換為數組并賦值給
elementData
- 如果集合返回的數組類型不是
Object[]
,會強制轉換(避免類型問題)
.
三、核心方法源碼分析
1. 添加元素:add(E e)
public boolean add(E e) {modCount++; // 修改次數 +1(用于迭代器 fail-fast)add(e, elementData, size);return true;
}private void add(E e, Object[] elementData, int s) {if (s == elementData.length)elementData = grow(); // 擴容elementData[s] = e;size = s + 1;
}
- 關鍵步驟:
- 檢查是否需要擴容
(s == elementData.length)
- 調用
grow()
方法擴容 - 將元素賦值到
elementData[s]
- 更新
size
- 檢查是否需要擴容
2. 擴容機制:grow()
private Object[] grow() {return grow(size + 1);
}private Object[] 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);return elementData = Arrays.copyOf(elementData, newCapacity);
}
- 擴容規則:
- 默認擴容:
newCapacity = oldCapacity + (oldCapacity >> 1)
(即 1.5 倍) - 特殊情況:如果擴容后仍不足(如一次性添加大量元素),直接設置為
minCapacity
- 最大容量限制:超過
MAX_ARRAY_SIZE
時調用hugeCapacity()
處理
- 默認擴容:
hugeCapacity(int minCapacity)
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0)throw new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
- 如果
minCapacity
超過MAX_ARRAY_SIZE
,則返回Integer.MAX_VALUE
,否則返回MAX_ARRAY_SIZE
3. 刪除元素:remove(int index)
public E remove(int index) {Objects.checkIndex(index, size); // 檢查索引合法性final Object[] es = elementData;@SuppressWarnings("unchecked") E oldValue = (E) es[index];int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(es, index+1, es, index, numMoved); // 左移元素es[--size] = null; // 幫助 GCreturn oldValue;
}
- 關鍵步驟:
- 檢查索引合法性
- 獲取目標元素
- 將右半部分元素左移(時間復雜度 O(n))
- 將最后一個元素置為 null,幫助垃圾回收
- 更新 size
4. 獲取元素:get(int index)
public E get(int index) {Objects.checkIndex(index, size);return (E) elementData[index];
}
- 特點:直接通過索引訪問數組元素,時間復雜度
O(1)
5. 修改元素:set(int index, E element)
public E set(int index, E element) {Objects.checkIndex(index, size);E oldValue = (E) elementData[index];elementData[index] = element;return oldValue;
}
- 特點:直接修改數組中指定位置的元素,時間復雜度
O(1)
.
四、性能優化技巧
1. 預估容量,避免頻繁擴容
使用 ArrayList(int initialCapacity)
構造函數,提前指定容量。如果容量不足,頻繁擴容會導致性能下降(每次擴容需復制數組)
2. 手動擴容:ensureCapacity(int minCapacity)
public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0 : DEFAULT_CAPACITY;if (minCapacity > minExpand) {modCount++;grow(minCapacity);}
}
- 在添加大量元素前,調用此方法預擴容,減少中間擴容次數
3. 清空集合:clear()
public void clear() {modCount++;for (int i = 0; i < size; i++)elementData[i] = null; // 幫助 GCsize = 0;
}
- 注意:
clear()
不會釋放數組空間,只是將size
置為 0 并將元素設為null
.
五、線程安全問題
1. 線程不安全
ArrayList
不是線程安全的,多線程環境下可能引發數據不一致或 ConcurrentModificationException
2. 解決方案
- 使用
Collections.synchronizedList
:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
- 使用
CopyOnWriteArrayList
(適合讀多寫少場景):
List<String> cowList = new CopyOnWriteArrayList<>();
.
六、Fail-Fast 機制
1. 原理
ArrayList
通過modCount
變量記錄集合的修改次數- 迭代器遍歷時會檢查
modCount
是否與創建迭代器時的值一致。如果不一致,拋出ConcurrentModificationException
2. 代碼示例
public Iterator<E> iterator() {return new Itr();
}private class Itr implements Iterator<E> {int expectedModCount = modCount;public boolean hasNext() {checkForComodification();return cursor != size;}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}
.
七、性能對比與使用建議
使用建議
- 高頻隨機訪問:優先使用
ArrayList
- 頻繁中間插入/刪除:使用
LinkedList
- 線程安全:使用
Collections.synchronizedList
或CopyOnWriteArrayList
八、其他實用方法
九、總結
十、擴展學習建議
- 源碼調試:使用 IDE(如 IntelliJ IDEA)逐步調試 ArrayList 的
add
、remove
等方法,觀察elementData
和size
的變化 - 版本差異:對比 JDK 8 和 JDK 17 的
ArrayList
源碼,觀察擴容策略、subList
實現等細節變化 - 進階主題:
ArrayList
與Vector
的區別(線程安全 vs 性能)subList
返回的視圖對原集合的影響ArrayList
在 JVM 內存模型中的表現(數組的連續性 vs 鏈表的離散性)