[英]removeAll ArrayList vs LinkedList performance
我對這個程序有疑問。
public class Main {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<String>();
for(int i=0; i<100; i++){
arrayList.add("ValueA");
arrayList.add("ValueB");
arrayList.add(null);
arrayList.add("ValueC");
arrayList.add(null);
arrayList.add(null);
}
long startTime = System.nanoTime();
arrayList.removeAll(Collections.singleton(null));
long endTime = System.nanoTime();
System.out.println("ArrayList removal took: " + (endTime - startTime) + "ms");
List<String> linkedList = new LinkedList<String>();
for(int i=0; i<100; i++){
linkedList.add("ValueA");
linkedList.add("ValueB");
linkedList.add(null);
linkedList.add("ValueC");
linkedList.add(null);
linkedList.add(null);
}
startTime = System.nanoTime();
linkedList.removeAll(Collections.singleton(null));
endTime = System.nanoTime();
System.out.println("LinkedList removal took: " + (endTime - startTime) + "ms");
}
}
系統輸出是:
刪除ArrayList:377953ms
LinkedList刪除采取:619807ms
為什么linkedList在removeAll上花費的時間比arrayList多?
正如Milkmaid所說,這不是你應該如何進行基准測試,但我相信你得到的結果仍然有效。
讓我們看看“幕后”並看到兩個實現:
ArrayList.removeAll
調用batchRemove
:
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
正如您所看到的, ArrayList
首先通過覆蓋需要使用之后的元素刪除的元素來“碎片化”底層數組( complement
作為false
傳遞,因此只復制非null
對象):
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
以下if (r != size)
處理從c.contains
拋出異常的情況,並使用“魔術函數” System.arraycopy
將其余元素從當前索引復制到結尾 - 此部分運行通過本機代碼,應該相當快,這就是為什么我們可以忽略它。
在最后一個if: if (w != size) {...}
它只是將null
s分配給列表的其余部分,以便GC能夠收集符合條件的對象。
操作總數為O(n)
,每個操作使用對陣列的直接訪問。
現在讓我們來看看LinkedList的實現,它相當短:
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove(); // <-- calls the iterator remove method
modified = true;
}
}
return modified;
}
如您所見,該實現使用迭代器來通過調用來刪除元素: it.remove();
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet); // <-- this is what actually runs
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
反過來調用:
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = l.remove(index+offset); // <-- here
this.modCount = l.modCount;
size--;
return result;
}
哪個電話:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index)); // <-- here
}
哪個叫:
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;
}
盡管理論上LinkedList中的remove
操作應該是O(1)
而ArrayList實現應該采用O(n)
,但是當處理批處理時,ArrayList的實現更簡潔,通過移動對象來覆蓋我們的那些,在一次通過中做所有事情刪除(碎片整理),而LinkedList的實現遞歸調用5個不同的方法(每個方法運行自己的安全檢查...),它刪除的每個元素,這最終會帶來你經歷的巨大開銷。
首先,100個元素不足以測試性能。 但是從理論上講:數組中的數據(通常)一個接一個地存儲在內存中。 在鏈表中,您有值,也有指向另一個對象的指針。 這意味着當你刪除數組時,你只需要通過連接的內存。 O contre如果從Linked list中刪除,則必須通過隨機片段內存取決於指針。 數組和鏈表之間存在更多差異。 喜歡添加元素刪除元素等。這就是為什么我們有數組和鏈表。 看一下Array vs Linked list
這個問題的答案歸結為for循環的執行時間的差異。 當您深入研究這兩個對象的removeAll()
代碼時,您會看到ArrayList
的removeAll()
調用batchRemove()
,它看起來像:
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
另一方面,當你調用LinkedList
的removeAll()
時,它會調用AbstractCollection
的removeAll()
,它看起來像:
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
可以清楚地看到,在ArrayList
情況下,與在LinkedList
基於for
循環的Iterator
相比,執行簡單的for
循環。
Iterator
更適合像LinkedList
這樣的數據結構,但它仍然比傳統的for
循環更慢。
您可以在此處了解有關這兩個循環的性能差異的更多信息。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.