在for循环中重用ArrayLists的有效方法是什么?

时间:2010-05-09 19:39:08

标签: java

我在for循环中重复使用相同的ArrayList,我使用

for loop
    results = new ArrayList<Integer>();
    experts = new ArrayList<Integer>();
    output = new ArrayList<String>();
....

创建新的。

我想这是错的,因为我正在分配新内存。它是否正确 ? 如果是,我该如何清空它们?

已添加:另一个例子

每次调用此方法时,我都会创建新变量。这是好习惯吗?我的意思是创造新的精度,相关的创造...等?或者我应该在我的课程中声明它们,在方法之外不分配越来越多的内存?

public static void computeMAP(ArrayList<Integer> results, ArrayList<Integer> experts) {

  //compute MAP
  double precision = 0;
  int relevantFound = 0;
  double sumprecision = 0;

感谢

5 个答案:

答案 0 :(得分:7)

ArrayList.clear()会为你清空它们;请注意,按照自己的方式进行操作也是“好的”,因为Java是垃圾收集的,因此旧的分配最终会被清理干净。尽管如此,最好避免大量的新分配(以及垃圾生成),因此更好的方法是将这些声明移出循环并在其中调用clear

对于你的第二个例子,无论哪种方式都没问题;原始类型通常只会被分配一次(在堆栈中,当您进入函数时),在循环内声明它们不会增加任何成本。它只是你需要担心的堆分配(即调用new)。

回应评论:
如果那些事情成为实例成员没有意义,那么就不要这样做。此外,使用new来“清除”它们意味着每次都要分配新对象;绝对不要这样做 - 如果你的方法在每次调用时需要一个新的东西副本,除了那个方法之外它没有被使用,那么它就没有业务是一个实例变量。
总的来说,在这一点上担心这种微观优化是适得其反的;你只考虑它,如果你真的,绝对必须,然后衡量在做任何事情之前是否有好处。

答案 1 :(得分:1)

首先,在java中分配原始类型实际上是免费的,所以不要担心。

关于对象,它实际上取决于循环。如果它是100k的紧密循环那么是的,每次循环时分配3个数组列表对象是一个大问题。最好将它们分配到循环之外并使用List.clear()。

您还必须考虑代码的运行位置。如果它是一个移动平台,你会比那些拥有256GB内存和64个CPU的服务器更关注频繁的垃圾收集。

所有人都说,无论平台如何,没有人会打败你的性能编码。性能通常与代码清洁度有关。例如,在Android平台上,他们建议使用for(int i = 0 ...)语法循环遍历数组列表与(Object o:someList)。后一种方法更清晰,但在移动平台上,性能差异很大。在这种情况下,我不认为明确()在循环之外使事情变得更难理解。

答案 2 :(得分:1)

下面的代码片段衡量在循环中分配新列表与调用clear()以重用现有列表之间的区别。

如上所述,分配新列表的速度较慢。这给出了多少的想法。

请注意,代码循环100,000次以获取这些数字。对于UI代码,差异可能无关紧要。对于其他应用程序,重用该列表可能是一项重大改进。

这是三次运行的结果:

Elapsed time - in the loop: 2198 
Elapsed time - with clear(): 1621

Elapsed time - in the loop: 2291 
Elapsed time - with clear(): 1621   

Elapsed time - in the loop: 2182 
Elapsed time - with clear(): 1605

话虽如此,如果列表中包含数百甚至数千个对象,则与对象的分配相比,数组本身的分配将变得苍白。性能瓶颈将与添加到阵列的对象有关,而与阵列无关。

为了完整性:使用Java 1.6.0_19测量代码,在带有Windows的Centrino 2笔记本电脑上运行。然而,重点是它们之间的差异,而不是确切的数字。

import java.util.*;

public class Main {
    public static void main(String[] args)    {

      // Allocates a new list inside the loop
      long startTime = System.currentTimeMillis();
      for( int i = 0; i < 100000; i++ ) {
         List<String> l1 = new ArrayList<String>();
         for( int j = 0; j < 1000; j++ )
            l1.add( "test" );
      }
      System.out.println( "Elapsed time - in the loop: " + (System.currentTimeMillis() - startTime) );

      // Reuse the list
      startTime = System.currentTimeMillis();
      List<String> l2 = new ArrayList<String>();
      for( int i = 0; i < 100000; i++ ) {
         l2.clear();
         for( int j = 0; j < 1000; j++ )
            l2.add( "test" );
      }
      System.out.println( "Elapsed time - with clear(): " + (System.currentTimeMillis() - startTime) );
    }
}

答案 3 :(得分:0)

ArrayLists为5个条目分配默认内存。这些条目是引用,每个条目需要4个字节(取决于体系结构,甚至可能是8个字节)。数组列表包含一个“实际”长度的int,它已经是24个字节。添加默认的16个字节,每个对象(即使没有实例变量)都有,所以每个ArrayList()至少有40个字节。根据是存储它们还是存储多少,这可能会导致性能下降。

然而,从Java 1.6.16开始,JVM不具有(默认关闭?)功能,如果对这些对象的访问权限不离开方法上下文,则该功能会“内联”函数中的对象。在这种情况下,所有实例变量都被编译为用作调用函数的“本地”实例变量,因此不会创建任何实际对象。

答案 4 :(得分:0)

此处需要考虑的另一个问题是如何影响垃圾收集。很明显,重用相同的ArrayList引用并使用ArrayList.clear()可以减少实例创建。

然而,垃圾收集并不是那么简单,显然我们在这里强迫“旧”对象引用“更新”的对象。这意味着更多从年代到年轻的参考(即从老一代的对象到年轻一代的对象的引用)。这种引用会在垃圾收集过程中产生更多的工作(例如,参见this article)。

我从未试图对此进行基准测试,但我不知道这有多重要,但我认为这可能与此讨论相关。也许如果列表项的数量明显超过列表的数量,则使用相同的列表是不值得的。