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

在遍历ArrayList,HashMap和其他集合的时候,有没有比较传统的for循环和迭代器的性能testing结果?

或者干脆为什么我应该使用Iterator over循环,反之亦然?

假设这是你的意思:

 // 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,性能差异应该可以忽略不计。

编辑:我相信微观基准是非常邪恶的根源,就像早期的优化。 但是,我认为,对于这些相当微不足道的东西的影响,我觉得很好。 因此我运行了一个小testing :

  • 迭代遍历LinkedList和ArrayList
  • 用10万个“随机”string
  • 总结它们的长度(只是为了避免编译器优化整个循环)
  • 使用所有3循环样式(迭代器,为每个,与计数器)

结果是相似的所有,但与LinkedList“与计数器”。 所有其他五个都花了不到20毫秒来遍历整个列表。 在LinkedList上使用list.get(i) 100,000次需要花费2分钟(!)才能完成(60,000次)。 哇! :)因此,最好使用迭代器(明确或隐含地使用每个迭代器),特别是如果你不知道你处理的列表的types和大小。

使用迭代器的第一个理由显然正确的 。 如果您使用手动索引,则可能会出现非常无害的逐个错误,只有仔细观察才能看出:您是从1还是从0开始? 你有没有完成length - 1 ? 你用<<= ? 如果您使用迭代器,则更容易看到它实际上遍历整个数组。 “说你做什么,做你的话。”

第二个原因是统一访问不同的数据结构。 一个数组可以通过一个索引被有效地访问,但是链接列表最好通过记住最后访问的元素来遍历(否则你会得到一个“ Shlemiel画家 ”)。 哈希映射更加复杂。 通过提供来自这些数据结构和其他数据结构的统一接口(例如,您也可以进行树遍历),您将再次获得明显的正确性。 遍历逻辑只能执行一次,使用它的代码可以简明扼要地说出它做了什么,并做它所说的事情。

性能在大多数情况下是相似的。

然而,每当一个代码收到一个List,并在其上循环,有一个众所周知的情况:
对于不实现RandomAccess的所有List实现 (例如:LinkedList) ,迭代器更好

原因在于对于这些列表,按索引访问元素不是一个常量的操作。

所以你也可以把Iterator看作更强大的(对于实现细节)。


一如既往,性能不应该被隐藏的可读性问题。
java5的foreach循环在这方面是一个很大的打击:-)

我不相信

 for (T obj : collection) { 

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

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

在i ++语法上使用迭代器的最好的理由之一是并不是所有的数据结构都支持随机访问,更不用说它的性能。 你也应该编程到列表或者收集界面,这样如果你以后决定另外一个数据结构会更有效率的话,你就可以在没有大规模手术的情况下把它换掉。 在这种情况下(编码到接口的情况),您不一定会知道实现的细节,将其推迟到数据结构本身可能更为明智。

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

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

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

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

是的,它确实对不像基于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 } 

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

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

+1 sfussenegger说什么。 仅供参考,无论您使用显式迭代器还是隐式迭代器(即针对每个)都不会因性能差异而编译为相同的字节码。