removeAll ArrayList vs LinkedList performance

时间:2015-12-11 06:05:42

标签: java performance arraylist linked-list

我对此计划有疑问。

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多?

3 个答案:

答案 0 :(得分:2)

正如Milkmaid所说,这不是你应该如何进行基准测试,但我相信你得到的结果仍然有效。

让我们来看看&#34;引擎盖&#34;并看到两个实现:

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首先&#34;碎片整理&#34;通过覆盖需要使用之后的元素移除的元素来覆盖基础数组(complement作为false传递,因此只复制非null的对象:

if (c.contains(elementData[r]) == complement)
    elementData[w++] = elementData[r];

以下if (r != size)处理从c.contains抛出异常并使用&#34; magic函数&#34; System.arraycopy将当前索引中的其余元素复制到结尾 - 这部分由本机代码运行,速度相当快,这就是为什么我们可以忽略它。

在最后一个if:if (w != size) {...}中,它只是将null分配给列表的其余部分,以便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&#39;实现是递归调用它删除的每个元素的5个不同的方法(每个方法都运行自己的安全检查...),这最终会带来你经历的巨大开销。

答案 1 :(得分:0)

首先,100个元素不足以测试性能。但是从理论上讲: 数组中的数据(通常)一个接一个地存储在存储器中。在链表中,您有值,也有指向另一个对象的指针。这意味着当你删除数组时,你只需要通过连接的内存。 O contre如果从Linked list中删除,则必须通过随机片段内存取决于指针。数组和链表之间存在更多差异。喜欢添加元素删除元素等。这就是为什么我们有数组和链表。看看Array vs Linked list

答案 2 :(得分:0)

这个问题的答案归结为for循环的执行时间的差异。当您深入研究这两个对象的ArrayList代码时,您会看到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; } 次调用removeAll(),如下所示:

LinkedList

另一方面,当您拨打removeAll()的{​​{1}}时,它会调用AbstractCollection的{​​{1}},如下所示:

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的情况下,可以清楚地看到,与for中基于Iterator的{​​{1}}循环相比,执行简单的for循环。

LinkedList对于像Iterator这样的数据结构更好,但它仍然比传统的LinkedList数组循环慢。

您可以详细了解这两个循环here的性能差异。