在构造函数中设置Java Collection的大小更好吗?

时间:2014-01-15 13:37:50

标签: java collections microbenchmark jmh

如果我知道那时的大小,是否最好将Collection的大小传递给Collection构造函数?扩展Collection和分配/重新分配显着的节约效果是什么?

如果我知道Collection的最小尺寸而不是上限,该怎么办?至少在最小尺寸下仍然值得创造吗?

5 个答案:

答案 0 :(得分:5)

不同的集合对此有不同的性能影响,对于ArrayList,保存可以非常明显。

import java.util.*;
public class Main{
public static void main(String[] args){
  List<Integer> numbers = new ArrayList<Integer>(5);
  int max = 1000000;
  // Warmup
  for (int i=0;i<max;i++) {
    numbers.add(i);
  }

  long start = System.currentTimeMillis();
  numbers = new ArrayList<Integer>(max);
  for (int i=0;i<max;i++) {
    numbers.add(i);
  }
  System.out.println("Preall: "+(System.currentTimeMillis()-start));

  start = System.currentTimeMillis();
  numbers = new ArrayList<Integer>(5);
  for (int i=0;i<max;i++) {
    numbers.add(i);
  }
  System.out.println("Resizing: "+(System.currentTimeMillis()-start));

}
}

结果:

Preall: 26
Resizing: 58

以max000设置为10000000处的值的10倍运行会产生:

Preall: 510
Resizing: 935

所以你甚至可以看到不同尺寸的比例保持不变。

这几乎是最糟糕的测试,但是一次填充一个数组元素非常常见,您可以看到速度差异大约为2 *。

答案 1 :(得分:4)

所有馆藏都在自动扩展。不知道边界不会影响它们的功能(直到你遇到其他问题,比如使用所有可用的内存等),但它可能会影响它们的性能。

有一些收藏品。最值得注意的是ArrayList,自动扩展是昂贵的,因为整个底层数组被复制;数组列表的默认大小为10,然后每次达到最大值时加倍。所以,假设你知道你的arraylist将包含110个对象,但是没有给它一个大小,会发生以下副本

复制10 - &gt; 20个
复制20 - &gt; 40个
复制40 - &gt; 80个
复制80 - &gt; 160

通过告诉arraylist它包含110个项目,你跳过这些副本。

有根据的猜测总比没有好

即使你错了也没关系。该集合仍将自动扩展,您仍然可以避免一些副本。你可以降低性能的唯一方法是你的猜测是否太大:这将导致分配给集合的内存太多

答案 2 :(得分:4)

好的,这是我的jmh代码:

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 3, time = 1)
@Fork(3)
public class Comparison
{
  static final int size = 1_000;
  @GenerateMicroBenchmark
  public List<?> testSpecifiedSize() {
    final ArrayList<Integer> l = new ArrayList(size);
    for (int i = 0; i < size; i++) l.add(1);
    return l;
  }

  @GenerateMicroBenchmark
  public List<?> testDefaultSize() {
    final ArrayList<Integer> l = new ArrayList();
    for (int i = 0; i < size; i++) l.add(1);
    return l;
  }
}

size = 10_000的结果:

Benchmark             Mode Thr    Cnt  Sec         Mean   Mean error    Units
testDefaultSize       avgt   1      9    1       80.770        2.095  usec/op
testSpecifiedSize     avgt   1      9    1       50.060        1.078  usec/op

size = 1_000的结果:

Benchmark             Mode Thr    Cnt  Sec         Mean   Mean error    Units
testDefaultSize       avgt   1      9    1        6.208        0.131  usec/op
testSpecifiedSize     avgt   1      9    1        4.900        0.078  usec/op

我的解释:

  • 预设在默认大小上有一些边缘;
  • 边缘并不那么壮观;
  • 在添加到列表的任务上花费的绝对时间是非常微不足道的。

我的结论:

添加初始尺寸,如果这会让您感觉更温暖,但客观地说,您的客户极不可能注意到差异。

答案 3 :(得分:0)

在极少数情况下,当大小众所周知时(例如,在将新数量的元素填入新集合时),可能会出于性能原因进行设置。

通常最好省略它并使用默认构造函数,从而导致更简单,更易理解的代码。

答案 4 :(得分:0)

对于基于数组的集合,重新调整大小是一项非常昂贵的操作。这就是为ArrayList传递确切大小的原因是个好主意。

如果您将大小设置为最小尺寸(MIN),然后添加到集合MIN + 1元素,那么您将重新调整大小。 ArrayList()调用ArrayList(10),因此如果MIN足够大,那么您将获得一些优势。但最好的方法是使用期待集合大小创建ArrayList

但是你可能更喜欢LinkedList,因为添加元素没有任何成本(虽然list.get(i)有O(i)成本)