今天看到有人提問 arrayList 與 LinkedList 性能比較的問題,爲此專門測試了下兩者的插入,刪除,訪問效率
經過測試大概得出以下結論:
ArrayList 與 LinkedList 在順序插入時(末尾插入),數據量較小時(100000以內)LinkedList的插入效率優於 arrayList(但不明顯,最多幾倍的差距),但數據量更大時(40w 以上)此時順序插入 arraylist 的插入性能明顯優於 linkedlist.但如果一直在 list 的位置0插入,linkedList 的插入性能對 arrayList 有指數級性能優勢.
可以查看兩者的插入代碼分析以上現象:
先看 arrayList 的插入代碼,主要代碼如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
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++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
分析以上代碼可以看到,arrayList 在順序插入時,如果數據容量不夠,會經常擴容,其中擴容代碼Arrays.copyOf(elementData, newCapacity);
會消耗大量時間,但如果數據量較大,此時擴容次數明顯下降(擴容總是會在當前容量的2倍),因此擴容消耗的時間平均下來明顯降低,但對於每次都插入指定位置0的時候System.arraycopy(elementData, index, elementData, index + 1,size - index);
每次都會執行這個代碼,數組的拷貝會浪費大量時間,因此插入時間會呈指數級增長
對於 linkedList,插入的主要代碼邏輯如下:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
可見對於末尾插入,linkedlist 每次打操作都是直接在鏈表末尾插入數據,對於位置0的插入,linkedlist 的操作與在末尾插入基本一致,(linkedlist 對於制定位置操作會首先檢查靠近末尾還是靠近頭元素,會從靠近的一端進行遍歷),因此 linkedList 對於中間部分的插入操作的耗時會基本花在查找元素上
對於插入操作,兩者可以得出以下結論:
1.對於少量元素,儘量使用 arrayList
2.對於大量元素(10w 以上),沒有頻繁的隨機讀取操作,且有大量的 list 前部的插入操作(前10%),此時可以選用 Linkedlist
3.儘量使用 arrayList,除非使用 LinkedList 時的速度是 arrayList 的10倍以上
對於刪除操作:
對於大量數據,如果一直從 list 開始位置開始刪除,linkedlist 對 arrayList 有指數級優勢,但如果從末尾位置開始刪除,arrayList 對 linkedList有較大優勢
arraylist刪除代碼如下
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; // clear to let GC do its work
return oldValue;
}
主要耗時代碼爲:System.arraycopy(elementData, index+1, elementData, index, numMoved);
,每一次刪除 arrayList 都需要將被刪除元素之後的所有元素複製一遍
對於 linkedList 的刪除代碼:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
對於 linkedList 來說刪除的主要耗時還是花在查找元素上
因此對於刪除操作這兩者的建議如下:
對於少量數據的刪除建議使用 arrayList
對於大量數據,總是刪除靠前數據時,建議使用 linkedList
其餘情況使用 arrayList
綜合以上插入與刪除,對於這兩種 list 的綜合建議:
99%的情況用ArrayList,還有1%的情況是:
1、超大List,超大指至少十幾萬個元素,並且還需要頻繁添加刪除(在 list 開始部分),但不需要頻繁訪問
2、當你發現ArrayList比LinkedList耗時多一個數量級的時候