頻繁在列表頭部/中間插入、刪除元素時,LinkedList 效能遠優於 ArrayList(可達 40-50 倍);但隨機存取(get/set)與尾部新增(add(E))場景下,ArrayList 更優。選型需根據業務寫操作分佈決策。
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底層結構 | 動態陣列 | 雙向鏈表 |
| 記憶體分佈 | 連續記憶體塊 | 離散節點,透過指標連結 |
| 擴容機制 | 1.5 倍擴容 + 陣列複製 | 無擴容,按需分配節點 |
| 隨機存取 | O(1) 陣列下標直達 | O(n) 需從頭/尾遍歷 |
| 插入/刪除 | O(n) 需搬移後續元素 | O(1) 僅修改鄰近節點指標 |
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // 1. 檢查擴容
System.arraycopy(elementData, index, elementData, index + 1, size - index); // 2. 核心搬移:將 index 及之後元素向後複製一位
elementData[index] = element; // 3. 賦值
size++;
}
痛點:System.arraycopy 雖為 Native 優化,但每次插入都需搬移 size - index 個元素。頭部插入(index=0)搬移全陣列,尾部插入(index=size)搬移 0 元素。頻繁頭部/中間插入導致大量記憶體複製與 GC 壓力。
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element); // 尾部新增優化
else
linkBefore(element, node(index)); // 中間/頭部插入
}
void linkBefore(E e, Node succ) {
final Node pred = succ.prev;
final Node newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
優勢:僅建立新節點、修改 prev/next 兩個指標,無陣列搬移,時間複雜度固定 O(1)(不含定位節點的 O(n) 遍歷)。
// ArrayList.remove(int index)
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved); // 搬移
elementData[--size] = null; // 釋放引用
// LinkedList.unlink(Node x)
E element = x.item;
Node next = x.next, prev = x.prev;
if (prev == null) first = next; else prev.next = next;
if (next == null) last = prev; else next.prev = prev;
x.item = null; x.next = null; x.prev = null; // GC 友善
size--;
modCount++;
return element;
同理,ArrayList 需搬移後續元素填補空缺;LinkedList 僅斷開重連指標。
// ArrayList.set(int index, E element) - O(1)
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
// LinkedList.set(int index, E element) - O(n)
Node x = node(index); // 折半遍歷定位
E oldVal = x.item;
x.item = element;
return oldVal;
LinkedList 修改元素需先遍歷定位節點(node(index) 採雙向折半遍歷),大量修改時效能遠遜於 ArrayList 的直接下標賦值。
測試環境:JDK 1.8、Intel i7、10 萬筆資料、循環 1000 次取平均
| 操作類型 | 位置 | ArrayList 耗時 | LinkedList 耗時 | 倍數差異 |
|---|---|---|---|---|
| 插入 | 頭部 | ~1200 ms | ~22 ms | LinkedList 快 54 倍 |
| 插入 | 中間 | ~600 ms | ~15 ms | LinkedList 快 40 倍 |
| 插入 | 尾部 | ~5 ms | ~6 ms | 持平 |
| 刪除 | 頭部 | ~1100 ms | ~18 ms | LinkedList 快 61 倍 |
| 刪除 | 中間 | ~550 ms | ~14 ms | LinkedList 快 39 倍 |
| 修改 | 隨機 | ~3 ms | ~450 ms | ArrayList 快 150 倍 |
| 隨機讀取 | 隨機 | ~2 ms | ~380 ms | ArrayList 快 190 倍 |
ArrayListLinkedListArrayList(預設首選)CopyOnWriteArrayList(讀多寫少)或 ConcurrentLinkedQueue(高併發佇列)ArrayList,效能瓶頸出現再針對性優化Deque 介面,offer/poll 為 O(1),確實適合佇列/棧ensureCapacity 或建構子指定初始容量可避免ArrayList 與 LinkedList 的本質權衡是 「空間換時間」(陣列連續記憶體換取 O(1) 隨機存取)與 「時間換空間」(鏈表離散節點換取 O(1) 插入刪除)。實務中 90% 以上場景 ArrayList 為優先選擇,僅在明確存在高頻頭部/中間寫操作時才考慮 LinkedList。切記:過早優化是萬惡之源,先測量再決策。