哪一个更有效率,一个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个项目并使用两种方式打印。 并打印执行的时间差。 你可以看到差异。