簡體   English   中英

removeAll ArrayList vs LinkedList性能

[英]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()代碼時,您會看到ArrayListremoveAll()調用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;
}

另一方面,當你調用LinkedListremoveAll()時,它會調用AbstractCollectionremoveAll() ,它看起來像:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM