哪一个更有效率,一个for-each循环还是一个迭代器?

哪个是遍历集合的最有效的方法?

List<Integer> a = new ArrayList<Integer>(); for (Integer integer : a) { integer.toString(); } 

要么

 List<Integer> a = new ArrayList<Integer>(); for (Iterator iterator = a.iterator(); iterator.hasNext();) { Integer integer = (Integer) iterator.next(); integer.toString(); } 

请注意,尽pipe最后一个问题的答案之一接近,但这不是这个 , 这个 , 这个或这个的完全重复。 这不是一个愚蠢的原因是,其中大部分是比较循环,你在循环内调用get(i) ,而不是使用迭代器。

正如Meta上的build议,我会发布我的答案这个问题。

如果你只是在集合上漫游来读取所有的值,那么使用迭代器或新的for循环语法就没有区别,因为新的语法只是使用迭代器在水下。

但是,如果你的意思是循环旧的“c风格”循环:

 for(int i=0; i<list.size(); i++) { Object o = list.get(i); } 

然后,新的for循环或迭代器可以更有效,这取决于底层的数据结构。 原因是对于某些数据结构, get(i)是一个O(n)操作,它使循环成为O(n 2 )操作。 传统的链表是这种数据结构的一个例子。 所有迭代器都有一个基本的要求, next()应该是O(1)操作,使得循环O(n)。

要validation迭代器在新的for循环语法中是否在水下使用,请比较以下两个Java片段中生成的字节码。 首先for循环:

 List<Integer> a = new ArrayList<Integer>(); for (Integer integer : a) { integer.toString(); } // Byte code ALOAD 1 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator; ASTORE 3 GOTO L2 L3 ALOAD 3 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object; CHECKCAST java/lang/Integer ASTORE 2 ALOAD 2 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String; POP L2 ALOAD 3 INVOKEINTERFACE java/util/Iterator.hasNext()Z IFNE L3 

其次,迭代器:

 List<Integer> a = new ArrayList<Integer>(); for (Iterator iterator = a.iterator(); iterator.hasNext();) { Integer integer = (Integer) iterator.next(); integer.toString(); } // Bytecode: ALOAD 1 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator; ASTORE 2 GOTO L7 L8 ALOAD 2 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object; CHECKCAST java/lang/Integer ASTORE 3 ALOAD 3 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String; POP L7 ALOAD 2 INVOKEINTERFACE java/util/Iterator.hasNext()Z IFNE L8 

正如你所看到的,生成的字节码是完全相同的,所以使用任何一种forms都没有性能损失。 因此,你应该select最美观的循环forms,对于大多数人来说,这将是for-each循环,因为它具有较less的样板代码。

差异不在于performance,而在于能力。 当直接使用引用时,你有更多的权力显式使用迭代器types(例如List.iterator()与List.listIterator(),尽pipe在大多数情况下它们返回相同的实现)。 你也有能力在循环中引用迭代器。 这允许您执行从集合中删除项目而不会收到ConcurrentModificationException的情况。

例如

还行吧:

 Set<Object> set = new HashSet<Object>(); // add some items to the set Iterator<Object> setIterator = set.iterator(); while(setIterator.hasNext()){ Object o = setIterator.next(); if(o meets some condition){ setIterator.remove(); } } 

这不是,因为它会抛出一个并发修改exception:

 Set<Object> set = new HashSet<Object>(); // add some items to the set for(Object o : set){ if(o meets some condition){ set.remove(o); } } 

为了扩展Paul自己的答案,他已经certificate,在特定的编译器(大概是Sun的javac?)上字节码是相同的,但是不同的编译器不能保证生成相同的字节码,对吗? 要查看两者之间的实际差异,我们直接find源代码并检查Java语言规范,特别是14.14.2,“增强的for语句” :

增强的for语句相当于表单的基本语句:

 for (I #i = Expression.iterator(); #i.hasNext(); ) { VariableModifiers(opt) Type Identifier = #i.next(); Statement } 

换句话说,JLS要求两者是等同的。 理论上这可能意味着字节码的边际差异,但实际上增强的for循环需要:

  • 调用.iterator()方法
  • 使用.hasNext()
  • 通过.next()使局部variables可用

换句话说,为了所有的实际目的,字节码将是相同的,或几乎相同的。 很难想象任何编译器的实现都会导致两者之间的显着差异。

如果您需要在循环中修改集合,则可能需要使用迭代器。 第一种方法会抛出exception。

 for (String i : list) { System.out.println(i); list.remove(i); // throws exception } Iterator it=list.iterator(); while (it.hasNext()){ System.out.println(it.next()); it.remove(); // valid here } 

Iterator是Java Collections框架中的一个接口,提供了遍历或迭代集合的方法。

当你的动机是遍历一个集合来读取它的元素时,迭代器和for循环的作用是相似的。

for-each只是迭代Collection的一种方法。

例如:

 List<String> messages= new ArrayList<>(); //using for-each loop for(String msg: messages){ System.out.println(msg); } //using iterator Iterator<String> it = messages.iterator(); while(it.hasNext()){ String msg = it.next(); System.out.println(msg); } 

for-each循环只能用于实现迭代器接口的对象。

现在回到for循环和迭代器的情况。

当你尝试修改一个集合时会有所不同。 在这种情况下,由于迭代器的快速失败属性,效率更高。 即。 它在迭代下一个元素之前检查底层集合结构中的任何修改。 如果find任何修改,它将抛出ConcurrentModificationException

(注意:迭代器的这个function只适用于java.util包中的集合类,因为它们本质上是故障安全的,所以它不适用于并发集合)

无论如何, foreach在引擎盖下使用迭代器。 它真的只是语法糖。

考虑下面的程序:

 import java.util.List; import java.util.ArrayList; public class Whatever { private final List<Integer> list = new ArrayList<>(); public void main() { for(Integer i : list) { } } } 

让我们用javac Whatever.java进行javac Whatever.java
并阅读main()的反汇编字节码,使用javap -c Whatever

 public void main(); Code: 0: aload_0 1: getfield #4 // Field list:Ljava/util/List; 4: invokeinterface #5, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 9: astore_1 10: aload_1 11: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 16: ifeq 32 19: aload_1 20: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 25: checkcast #8 // class java/lang/Integer 28: astore_2 29: goto 10 32: return 

我们可以看到,这个foreach编译成一个程序:

  • 使用List.iterator()创build迭代器
  • 如果Iterator.hasNext() :调用Iterator.next()并继续循环

至于“为什么这个无用的循环在编译后的代码中得到了优化呢?我们可以看到它对列表项没有任何作用”:好吧,你可以编写你的迭代器,使得.iterator()有副作用,或者.hasNext()有副作用或有意义的后果。

你可以很容易地想象,一个表示数据库中可滚动查询的迭代器可能会对.hasNext() (如联系数据库或closures游标,因为已经到达结果集的末尾.hasNext()做了一些戏剧性的事情。

所以,尽pipe我们可以certificate在循环体中没有任何事情发生,但是当我们迭代时,certificate没有什么有意义的或必然的事情会更加昂贵(难以处理)。 编译器必须在程序中保留这个空的循环体。

我们希望得到的最好的结果是编译器警告 。 有趣的是, javac -Xlint:all Whatever.java没有警告我们这个空的循环体。 IntelliJ IDEA虽然。 不可否认,我已经configuration了IntelliJ来使用Eclipse编译器,但这可能不是原因。

在这里输入图像描述

在使用Collections时,我们应该避免使用传统的for循环。 我给出的简单原因是for循环的复杂度为O(sqr(n)),迭代器的复杂度或者增强for循环的复杂度只是O(n)。 所以它会带来性能上的差异。只要列出大约1000个项目并使用两种方式打印。 并打印执行的时间差。 你可以看到差异。