Java 为什么插入空ArrayList比插入非空ArrayList花费更多的时间?

Java 为什么插入空ArrayList比插入非空ArrayList花费更多的时间?,java,arraylist,nanotime,Java,Arraylist,Nanotime,我运行了1000次profiler程序来计算插入空ArrayList的平均时间和插入非空ArrayList的平均时间。 我为ArrayList运行了探查器程序,其中Item类是 class Item{ int id; String name; } 探查器程序类似于 main(){ final int iterations = 1000; /**** Empty List Insert ****/ int id = 0; long[] timerecords = new

我运行了1000次profiler程序来计算插入
空ArrayList
的平均时间和插入
非空ArrayList
的平均时间。 我为
ArrayList
运行了探查器程序,其中
Item
类是

class Item{
  int id;
  String name;
}
探查器程序类似于

main(){
  final int iterations = 1000;

  /**** Empty List Insert ****/
  int id = 0;
  long[] timerecords = new long[iterations];
  ArrayList<Item> list = new ArrayList<>();
  for (int i = 0; i < iterations; i++) {
      long stime = System.nanoTime(); //Start Time
      list.add(new Item(id, "A"));
      long elapsedTime = System.nanoTime() - stime; //Elapsed time
      timerecords[i] = elapsedTime; //Record Elapsed time

      //reset
      list.clear();
  }
  System.out.println("Empty List Insert Takes = " + getAverageTime(timerecords) + " nanoseconds");

  /**** Non-Empty List Insert ****/
  list = new ArrayList<>();
  timerecords = new long[iterations];
  //Insert some Items
  list.add(new Item(id++, "B")); list.add(new Item(id++, "R"));
  list.add(new Item(id++, "H")); list.add(new Item(id++, "C")); 

  for (int i = 0; i < iterations; i++) {    
        Item item = new Item(id, "A");
        long stime = System.nanoTime(); //Start Time
        list.add(item);
        long elapsedTime = System.nanoTime() - stime; //Elapsed time
        timerecords[i] = elapsedTime; //Record Elapsed time

        //reset
        list.remove(item);
  }
  System.out.println("Non-Empty List Insert Takes = " + getAverageTime(timerecords) + " nanoseconds");
}

这背后的原因是什么?

如果您研究一下
ArrayList
的实现,空的ArrayList从一个空数组开始存储数据-这很容易

但是,当您第一次调用
add()
方法时,需要复制此数组-因此需要分配内存。
ensureCapacity()
grow()
方法尝试最小化
数组的创建。copyOf()
,但只要内部数组大小仍然很小,就必须越来越多地增加此数组,这可能会导致持续时间增加

下面是1.7版的
ArrayList.grow(int)

  private void grow(int minCapacity) {
    int oldCapacity = this.elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if(newCapacity - minCapacity < 0) {
      newCapacity = minCapacity;
    }

    if(newCapacity - 2147483639 > 0) {
      newCapacity = hugeCapacity(minCapacity);
    }

    this.elementData = Arrays.copyOf(this.elementData, newCapacity);
  }
private void grow(int minCapacity){
int oldCapacity=this.elementData.length;
int newCapacity=oldCapacity+(oldCapacity>>1);
if(新容量-最小容量<0){
新容量=最小容量;
}
如果(新容量-2147483639>0){
新容量=大容量(最小容量);
}
this.elementData=Arrays.copyOf(this.elementData,newCapacity);
}

您没有测量创建非空ArrayLIst所需的时间,是吗

如果您研究
ArrayList
的实现,空的ArrayList从一个空数组开始存储数据,这很容易

但是,当您第一次调用
add()
方法时,需要复制此数组-因此需要分配内存。
ensureCapacity()
grow()
方法尝试最小化
数组的创建。copyOf()
,但只要内部数组大小仍然很小,就必须越来越多地增加此数组,这可能会导致持续时间增加

下面是1.7版的
ArrayList.grow(int)

  private void grow(int minCapacity) {
    int oldCapacity = this.elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if(newCapacity - minCapacity < 0) {
      newCapacity = minCapacity;
    }

    if(newCapacity - 2147483639 > 0) {
      newCapacity = hugeCapacity(minCapacity);
    }

    this.elementData = Arrays.copyOf(this.elementData, newCapacity);
  }
private void grow(int minCapacity){
int oldCapacity=this.elementData.length;
int newCapacity=oldCapacity+(oldCapacity>>1);
if(新容量-最小容量<0){
新容量=最小容量;
}
如果(新容量-2147483639>0){
新容量=大容量(最小容量);
}
this.elementData=Arrays.copyOf(this.elementData,newCapacity);
}

您没有测量创建非空ArrayLIst所需的时间,是吗

差异来自实例创建(new Item())

创建时间变量的点是计时器开始的点。 您肯定正在将新项目添加到列表中。但在第一种情况下,在您执行System.nanoTime()之前;在添加(分配stime变量后)时,您正在调用项上的新运算符,这会占用额外的时间。 在第二种情况下,您在执行System.nanoTime()之前执行该操作


因此,只捕获向ArrayList添加item对象的时间。

差异来自实例创建(new item())

创建时间变量的点是计时器开始的点。 您肯定正在将新项目添加到列表中。但在第一种情况下,在您执行System.nanoTime()之前;在添加(分配stime变量后)时,您正在调用项上的新运算符,这会占用额外的时间。 在第二种情况下,您在执行System.nanoTime()之前执行该操作


因此,只捕获了向ArrayList添加item对象的时间。

我很感兴趣,所以对它进行了一些调试。这种影响来自两个不同的来源:

正如@Alexander已经指出的,不断增加Arraylist大小的容量会压缩您的测试。要“修复”此问题,只需创建具有足够资源的ArrayList,如:

ArrayList<Item> list = new ArrayList<>(iterations * 2);
ArrayList list=新的ArrayList(迭代次数*2);
即使这样做了,您也可以在第一个和第二个测试用例之间观察到100-300%的性能差异。原因与Java内部结构有关,即类加载和JIT。通过在第一次测试前进行一轮热身,如下所示:

    int rounds = 10000;
    long[] t = new long[rounds];
    ArrayList<Item> warmUpList = new ArrayList<>(rounds);
    for (int i = 0; i < rounds; i++) {
        warmUpList.add(new Item(0, "A"));
        long stime = System.nanoTime(); // Start Time
        long elapsedTime = System.nanoTime() - stime; // Elapsed time
        t[i] = elapsedTime; // Record Elapsed time
    }
int轮=10000;
长[]t=新长[轮];
ArrayList warmUpList=新ArrayList(轮);
对于(int i=0;i

您可以使用优化器。通过将“round”变量的值从100增加到10.000,您可以看到您自己的测试是如何获得越来越好的性能,而不是以相同的速度稳定下来的。

我很感兴趣,所以我对它进行了一些调试。这种影响来自两个不同的来源:

正如@Alexander已经指出的,不断增加Arraylist大小的容量会压缩您的测试。要“修复”此问题,只需创建具有足够资源的ArrayList,如:

ArrayList<Item> list = new ArrayList<>(iterations * 2);
ArrayList list=新的ArrayList(迭代次数*2);
即使这样做了,您也可以在第一个和第二个测试用例之间观察到100-300%的性能差异。原因与Java内部结构有关,即类加载和JIT。通过在第一次测试前进行一轮热身,如下所示:

    int rounds = 10000;
    long[] t = new long[rounds];
    ArrayList<Item> warmUpList = new ArrayList<>(rounds);
    for (int i = 0; i < rounds; i++) {
        warmUpList.add(new Item(0, "A"));
        long stime = System.nanoTime(); // Start Time
        long elapsedTime = System.nanoTime() - stime; // Elapsed time
        t[i] = elapsedTime; // Record Elapsed time
    }
int轮=10000;
长[]t=新长[轮];
ArrayList warmUpList=新ArrayList(轮);
对于(int i=0;i
你可以解放军