为什么ArrayList.addAll(…)不会检查给定的集合是否非空?

huangapple go评论86阅读模式
英文:

Why ArrayList.addAll(...) doesn't check the given colleciton for nonempty?

问题

我们发现ArrayList.addAll(...)方法不会检查给定的集合是否为空。这意味着即使我们实际上不需要,也会重新分配内存(将调用System.arrayCopy(...))。

我们是否应该添加一个IF检查以进行优化?

例如:第一段代码的运行时间比第二段代码快了8倍以上。
看起来我们的优化效果很好,那么为什么它之前没有被实现呢?

List<Integer> existed = new ArrayList<>();
List<Integer> empty = new ArrayList<>();
for (int i = 0; i < 100_000_000; i++) {
    if(!empty.isEmpty())
       existed.addAll(empty);
}

对比

List<Integer> existed  = new ArrayList<>();
List<Integer> empty = new ArrayList<>();
for (int i = 0; i < 100_000_000; i++) {
    existed.addAll(empty);
}
英文:

We found out that ArrayList.addAll(...) method doesn't check if the given collection is not empty.
It means we will re-allocate memory (System.arrayCopy(...) will be called) even if we don't actually need it.

Shoud we add an IF check for an optimization?

For example: elapsed time for the first code is more than 8 times faster than the second one.
It seems like our optimization works properly, so why it wasn't implemented already?

List&lt;Integer&gt; existed = new ArrayList&lt;&gt;();
List&lt;Integer&gt; empty = new ArrayList&lt;&gt;();
for (int i = 0; i &lt; 100_000_000; i++) {
    if(!empty.isEmpty())
       existed.addAll(empty);
}

VS

List&lt;Integer&gt; existed  = new ArrayList&lt;&gt;();
List&lt;Integer&gt; empty = new ArrayList&lt;&gt;();
for (int i = 0; i &lt; 100_000_000; i++) {
    existed.addAll(empty);
}

答案1

得分: 1

在JDK 14中,ArrayList.addAll() 方法会将要添加的集合作为数组进行复制,并且会增加ArrayList的修改计数 - 即使没有要添加的元素。

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    ...

因此,如果你的测试用例在添加元素之前没有使用isEmpty()方法,它将会导致不必要地分配 1 亿个新的 Object[0] 实例,并且会相应地增加相同次数的修改计数,这也可以解释为什么代码看起来较慢。

需要注意的是,只有当数组的大小超过现有数组的容量时,才会调用 System.arrayCopy 方法。

我刚才扫描了一下我的代码,我怀疑是否添加isEmpty()测试会加速代码,因为通常每次都有一些要添加的内容,因此添加这些额外的检查可能是不必要的。你需要根据自己的情况来决定。

英文:

In JDK14 ArrayList.addAll() makes a copy of the collection to add as an array and increments the ArrayList modification count - even if there are no elements to add.

public boolean addAll(Collection&lt;? extends E&gt; c) {
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    ...

So your testcase which is not using isEmpty() beforehand, it will cause unnecessary allocation of 100,000,000 instances of new Object[0] and increments the modification count same number of times and which would explain why it appears slower.

Note that System.arrayCopy is not called unless the array is growing beyond the capacity of the existing array.

I've scanned in my own code just now and I doubt whether adding the isEmpty() test would speed up the code as there is generally something to add each time, and so putting in these extra checks would unnecessary. You'd need to decide for your own situation.

huangapple
  • 本文由 发表于 2020年10月2日 23:48:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/64174557.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定