Java中传统for循环与Iterator / foreach的性能

时间:2009-12-10 07:39:16

标签: java map iterator arraylist for-loop

在遍历ArrayList,HashMap和其他集合时,是否有任何性能测试结果可用于比较传统的for循环与Iterator?

或者我为什么要将Iterator用于循环,反之亦然?

9 个答案:

答案 0 :(得分:80)

假设这就是你的意思:

// traditional for loop
for (int i = 0; i < collection.size(); i++) {
  T obj = collection.get(i);
  // snip
}

// using iterator
Iterator<T> iter = collection.iterator();
while (iter.hasNext()) {
  T obj = iter.next();
  // snip
}

// using iterator internally (confirm it yourself using javap -c)
for (T obj : collection) {
   // snip
}

对于没有随机访问的集合(例如TreeSet,HashMap,LinkedList),迭代器更快。对于数组和ArrayLists,性能差异应该可以忽略不计。

编辑:我认为微基准是非常邪恶的根源,就像早期优化一样。但话说回来,我觉得对这些相当琐碎的事情的影响感觉很好。因此我运行a small test

  • 分别遍历LinkedList和ArrayList
  • 100,000“随机”字符串
  • 总结它们的长度(只是为了避免编译器优化整个循环)
  • 使用所有3种循环样式(迭代器,每种循环样式,用于计数器)

除了“with with counter”与LinkedList之外的所有结果都相似。所有其他五个花费不到20毫秒来迭代整个列表。在LinkedList上使用list.get(i) 100,000次需要超过2分钟(!)才能完成(慢60,000次)。哇! :)因此,最好使用迭代器(显式或隐式使用每个),特别是如果你不知道你处理的列表的类型和大小。

答案 1 :(得分:22)

使用迭代器的第一个原因是显而易见的正确性。如果您使用手动索引,可能会出现非常无害的逐个错误,只有在您仔细观察时才能看到这些错误:您是从1开始还是从0开始?你在length - 1结束了吗?您使用的是<还是<=?如果使用迭代器,则更容易看出它实际上正在迭代整个数组。 “说出你做了什么,做你说的话。”

第二个原因是对不同数据结构的统一访问。可以通过索引有效地访问数组,但最好通过记住访问的最后一个元素来遍历链表(否则您将获得“Shlemiel the painter”)。哈希映射更复杂。通过从这些和其他数据结构提供统一的接口(例如,您也可以进行树遍历),您再次获得明显的正确性。遍历逻辑只需实现一次,使用它的代码可以简洁地“说出它的作用,并按照它所说的去做。”

答案 2 :(得分:4)

大多数情况下表现相似。

然而,每当代码收到一个List并在其上循环时,就会出现众所周知的情况:
Iterator对于所有未实现RandomAccess的List实现更好(例如:LinkedList)。

原因是对于这些列表,按索引访问元素不是一个恒定的时间操作。

因此,您也可以将Iterator视为更强大(对于实现细节)。


与往常一样,性能不应隐藏可读性问题 java5 foreach循环在这方面是一个很大的打击: - )

答案 3 :(得分:2)

我不相信

for (T obj : collection) {

每次通过循环计算.size()因此比

更快
for (int i = 0; i < collection.size(); i++) {

答案 4 :(得分:1)

对生成的代码使用JADJD-GUI,您会发现没有真正的区别。新迭代器形式的优点是它在代码库中看起来更干净。

编辑:我从其他答案中看到你实际上意味着使用get(i)和迭代器之间的区别。我把原始问题用来表示使用迭代器的旧方法和新方法之间的区别。

使用get(i)并维护自己的计数器,特别是对于List类,不是一个好主意,原因在于接受的答案中提到的原因。

答案 5 :(得分:1)

使用迭代器而不是i ++语法的最佳理由之一是,并非所有数据结构都支持随机访问,更不用说让它运行良好。您还应该对列表或集合界面进行编程,这样如果您以后确定另一个数据结构更有效,您就可以在没有大规模手术的情况下将其交换掉。在这种情况下(编码到接口的情况),您不一定知道实现细节,并且将其推迟到数据结构本身可能更明智。

答案 6 :(得分:1)

我学会坚持使用每个原因的原因之一是它简化了嵌套循环,特别是超过2维循环。你可能最终操纵的所有i,j和k都会很快混淆。

答案 7 :(得分:1)

是的,它确实对基于LinkedList的非随机访问的集合产生了影响。内部链表由指向下一个节点的节点实现(从头节点开始)。

链表中的get(i)方法从头节点开始,一直导航到第i个节点。当您使用传统的for循环遍历链表时,每次都从头节点重新开始,因此整个遍历变为二次时间。

for( int i = 0; i< list.size(); i++ ) {
    list.get(i); //this starts everytime from the head node instead of previous node
}

虽然for循环遍历从链表获得的迭代器并调用其next()方法。迭代器维护上次访问的状态,因此不会每次都从头开始。

for( Object item: list ) {
    //item element is obtained from the iterator's next method.
}

答案 8 :(得分:0)

+1 sfussenegger说的话。 FYI,无论是使用显式迭代器还是隐式迭代器(即每个)都不会产生性能差异,因为它们编译为相同的字节代码。