在 Java 開發中,ArrayList 和 LinkedList 是最常用的兩種 List 實現。雖然它們都實現了 List 接口,提供相似的 API,但底層數據結構的差異導致了截然不同的性能特徵。理解這些差異,能幫助我們在具體業務場景中做出正確的選型決策,避免性能瓶頸。
ArrayList 內部維護一個 Object[] elementData 數組。初始容量默認為 10,當元素數量超過容量時,會觸發擴容機制:新容量 = 舊容量 × 1.5 + 1。這意味著隨機訪問(get(index)、set(index, element))的時間複雜度為 O(1),因為可直接通過下標定位內存地址。
LinkedList 內部定義了靜態內部類 Node<E>,每個節點包含 item(元素值)、next(後驅指針)、prev(前驅指針)。鏈表不需要連續內存空間,插入/刪除只需修改相鄰節點的指針引用,時間複雜度為 O(1)(前提是已定位到目標節點)。但隨機訪問需要從頭或尾遍歷,時間複雜度為 O(n)。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
測試環境:50,000 個有序整數,執行 50,000 次 Collections.binarySearch()。
| 集合類型 | 耗時(毫秒) | 性能比率 |
|---|---|---|
| ArrayList | ~15 ms | 基準 (1x) |
| LinkedList | ~2,600 ms | 約 173 倍慢 |
原因分析:二分查找需要頻繁隨機訪問中間元素。ArrayList 直接下標定位,LinkedList 每次都需從頭/尾遍歷至中間,時間與列表大小成正比。
測試:在列表頭部重複插入 50,000 次元素(list.add(0, obj))。
| 集合類型 | 耗時(毫秒) | 性能比率 |
|---|---|---|
| ArrayList | ~2,460 ms | 基準 (1x) |
| LinkedList | ~15 ms | 約 164 倍快 |
原因分析:ArrayList 頭部插入需將所有現有元素向後移動(System.arraycopy),涉及大量數據拷貝;LinkedList 僅需創建新節點並調整頭節點指針,開銷固定。
new ArrayList<>(expectedSize)),避免多次擴容拷貝。每個元素額外佔用 兩個引用(prev/next)+ 對象頭。對於 1,000 個 Integer 元素:
ArrayList:約 4KB(數組引用)+ 對象內存LinkedList:約 24KB(1,000 個 Node 對象,每個 ~24 字節)+ 元素內存結論:小數據量時 LinkedList 內存開銷顯著更大;大數據量時 ArrayList 擴容可能產生臨時大數組導致 GC 壓力。
| 操作類型 | ArrayList | LinkedList | 備註 |
|---|---|---|---|
尾部添加 add(e) | 優(攤銷 O(1)) | 優(O(1)) | 均為常數時間 |
頭部/中間插入 add(index, e) | 差(O(n)) | 優(O(1) 定位後) | ArrayList 需移動元素 |
頭部/中間刪除 remove(index) | 差(O(n)) | 優(O(1) 定位後) | ArrayList 需移動元素填坑 |
隨機訪問 get(index) | 極優(O(1)) | 差(O(n)) | 核心差異點 |
迭代器遍歷 iterator() | 極優 | 極優 | 均為順序訪問 |
反轉 Collections.reverse() | 優 | 優 | LinkedList 雙向鏈表天然優勢 |
Deque 接口)Collections.synchronizedList() 或 CopyOnWriteArrayList,或考慮 ConcurrentLinkedQueue。ArrayList 大量 arraycopy,預估容量可顯著提升吞吐。ArrayList 與 LinkedList 的本質差異在於數組的連續內存 vs 鏈表的離散節點。這決定了:
實際開發中,90% 以上場景首選 ArrayList。只有在明確需要頻繁頭部/中間增刪、或作為隊列/棧使用時,才考慮 LinkedList。記住:針對抽象編程(面向 List 接口),未來遷移成本極低。