当我遍历List时,哪种方法最有效?

时间:2010-07-13 09:13:00

标签: java performance

List<T> list = new ArrayList<T>();

1方法:

for(int i = list.length - 1; i >= 0; i--) {
  System.out.println(list.get(i));
}

2方法:

for(T t : list) {
  System.out.println(t);
}

3方法:

Iterator<T> it = list.iterator();     
while(it.hasNext()) {
  System.out.println(it.next());
}

9 个答案:

答案 0 :(得分:12)

效率不太可能显着 - 当然System.out.println更可能是您特定示例中的瓶颈。

第二种方法(针对循环增强)是最可读。请注意,这三种方法不会做同样的事情 - 第一种方法将从 end 而不是 start 进行迭代。获得正确的行为几乎总是胜过少量,快一点。您的代码越可读,您就越有可能做到正确。

寻求可读性,衡量应用的性能,如果成为问题,微观优化瓶颈(继续衡量每一步)。

编辑:我的回答是在问题的第一行,显示正在使用ArrayList<T>

如果您想要任何 List<T>的答案,那么根本就没有准确的答案。 List<T>不提供任何有关复杂性的保证。它没有说明你期望get的速度有多快,也没有说你期望迭代器的速度有多快。您可以使用具有良好随机访问权限的实现,但迭代速度非常慢。这不太可能,但它仍然是有效的List<T>

简而言之:如果你担心效率,你需要知道你正在使用什么样的列表。 通常迭代它可能是合理有效的,并且随机访问可能会或可能不会有效。 (它可能比迭代更有效,但它不太可能显着更高效。)在实践中,我的经验是,在大多数应用程序中,你实际上有足够的知识来做出合理的判断......但是我通常仍然会首先编写可读性代码。

正如其他人所指出的,如果你想要使用随机访问来获取元素,那么确保你的列表也实现RandomAccess接口是值得的。 / p>

答案 1 :(得分:5)

如果你有一个没有随机访问支持的长列表,第一种方法会慢得多。例如。在LinkedList上调用get(1000000)将必须从列表的开头循环1000000个元素以到达1000000th元素。第二种和第三种方法应该是等价的。

如果它与您的情况相关,那么提供对其元素的持续时间随机访问的List实现应该重写java.util.RandomAccess标记接口。至少标准API中的List实现可以。

答案 2 :(得分:5)

方法1不太可能是最好的方法,因为它没有其他方面的可读性或性能优势。

如果您想跟踪索引,请考虑使用ListIterator代替Iterator,例如。

ListIterator<T> it = list.listIterator();
while (it.hasNext()) {
    T t = it.next;
    int index = it.previousIndex();
    System.out.printf("%d => %s%n", index, t);
}

正如其他人所指出的,方法3相当于方法2,除非你想在迭代过程中从列表中删除元素。

如果分析显示列表的get(int)方法或迭代器的next()hasNext()是性能瓶颈(非常罕见但可能发生),请考虑用数组替换列表(您仍然可以使用for-each语法,类似于方法2.)

答案 3 :(得分:2)

RandomAccess

java.util.ArrayList实施RandomAccess。文档清楚地表明了这意味着什么:

  

List实现使用的标记接口,表示它们支持快速(通常是恒定时间)随机访问。 [...]根据经验,List实现应该实现此接口,如果对于类的典型实例,此循环:

for (int i=0, n=list.size(); i < n; i++)
     list.get(i);
     

比这个循环运行得更快:

for (Iterator i=list.iterator(); i.hasNext(); )
     i.next();

因此,对于正确 List的{​​{1}},索引 - implements RandomAccess更快。

但请注意,对于get来说, NOT 为true,其中上述代码表现出二次性能。这是因为LinkedList不允许进行常时随机访问;也就是说,LinkedListget是线性的。

请注意,即使索引 - LinkedList更快,它也只会是一个常数因子。首先查看此优化尝试是否值得。

相关问题


get vs for-each循环

您应该关注这两种结构的性能没有显着差异。 Iterator循环更具可读性,但适用于更有限的场景,因此恰恰在这些场景中应该使用它们。如果for-each循环不适用,请使用for-each循环。

引用 Effective Java 2nd Edition,第46项:首选Iterator循环到传统的for-each循环

  

1.5版中引入的for循环消除了混乱   以及隐藏迭代器或索引变量的错误机会   完全。由此产生的习语同样适用于集合和数组:

for-each
     

当您看到冒号(:)时,请将其读作“in”。因此,上面的循环读作为   “对于元素中的每个元素// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); } 。”请注意,没有性能损失   使用e循环,甚至是数组。事实上,它可能会提供轻微的   在某些情况下,优于普通for-each循环的性能优势   只计算一次数组索引的限制。

另见

相关问题

答案 4 :(得分:1)

由于没有解释有效性的确切含义,我将从应该优先考虑的位置回答。

我希望第二种方法是最具可读性的

为什么:在编写程序之前,你不知道瓶颈会在哪里。这就是为什么过早优化不太可能产生实质性结果的原因。但是代码的可读性总是相关的,所以我会选择第二种变体。

答案 5 :(得分:1)

如果您关注性能,则取决于List的实现。 使用ArrayList,第一个是最快的。它和以下一样快:

for(int i = 0, n=list.size(); i < n; i++) {
  System.out.println(list.get(i));
}

那是因为ArrayList缓存了一个数组。当您致电objArrayList.get(i)时,它会返回objArrayList.buffArray[i]

对于每个和Iterator是相同的。因此同样缓慢

答案 6 :(得分:0)

我更喜欢方法2,除非你需要在迭代器对象上调用一些东西(比如iterator.remove()),在这种情况下我更喜欢方法3.

我将使用方法1的唯一情况是,无论如何我需要在循环中保持索引的跟踪(然后仅在List的情况下是密集的数组实施,如​​ArrayList)。

<强>更新 与评论/其他答案一致,我现在更喜欢ListIterator在迭代List跟踪索引时。使方法1仅对通过任何其他方法无法实现的方法有用。

答案 7 :(得分:0)

我发现this比较或许可以帮助你转发

答案 8 :(得分:0)

许多因素都会影响这项措施。

首先,请注意您的第二个和第三个备选方案是字节码等效的。实际上,Java enhanced for loop是一个语法糖,这意味着,在你的情况下,第二种选择将在编译后看起来像完全,就像你的第三种选择。

最明显的一个是所选择的实施。如果使用LinkedList而不是ArrayList进行此测试,您会发现显然迭代器比随机数组访问更快。在某些情况下,您甚至会发现迭代LinkedList比迭代ArrayList更快。现在你怎么做?在你的大部分代码中,你都会毫无理由地从一个代码转换到另一个代价。

因此,作为一般经验法则,对于可读性和代码效率,我倾向于总是使用第二种方法。