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

时间:2022-01-17 04:17:07

Which is the most efficient way to traverse a collection?

遍历集合的最有效方法是什么?

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

or

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

Please note, that this is not an exact duplicate of this, this, this, or this, although one of the answers to the last question comes close. The reason that this is not a dupe, is that most of these are comparing loops where you call get(i) inside the loop, rather than using the iterator.

请注意,这不是这个,这个,这个,或者这个的精确复制,尽管最后一个问题的答案很接近。这不是一个dupe的原因是,其中大多数是比较循环,而不是在循环中调用get(i),而不是使用迭代器。

As suggested on Meta I will be posting my answer to this question.

如梅塔所言,我将公布我对这个问题的答案。

8 个解决方案

#1


245  

If you are just wandering over the collection to read all of the values, then there is no difference between using an iterator or the new for loop syntax, as the new syntax just uses the iterator underwater.

如果您只是浏览集合以读取所有的值,那么使用迭代器或新的for循环语法没有区别,因为新的语法只是在水下使用迭代器。

If however, you mean by loop the old "c-style" loop:

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

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

Then the new for loop, or iterator, can be a lot more efficient, depending on the underlying data structure. The reason for this is that for some data structures, get(i) is an O(n) operation, which makes the loop an O(n2) operation. A traditional linked list is an example of such a data structure. All iterators have as a fundamental requirement that next() should be an O(1) operation, making the loop O(n).

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

To verify that the iterator is used underwater by the new for loop syntax, compare the generated bytecodes from the following two Java snippets. First the for loop:

为了验证新的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

And second, the iterator:

第二,迭代器:

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

As you can see, the generated byte code is effectively identical, so there is no performance penalty to using either form. Therefore, you should choose the form of loop that is most aesthetically appealing to you, for most people that will be the for-each loop, as that has less boilerplate code.

如您所见,生成的字节代码实际上是相同的,因此使用任何一种形式都不会造成性能损失。因此,您应该选择最美观的循环形式,对于大多数人来说,它是for-each循环,因为它的样板代码更少。

#2


95  

The difference isn't in performance, but in capability. When using a reference directly you have more power over explicitly using a type of iterator (e.g. List.iterator() vs. List.listIterator(), although in most cases they return the same implementation). You also have the ability to reference the Iterator in your loop. This allows you to do things like remove items from your collection without getting a ConcurrentModificationException.

区别不在于性能,而在于能力。当直接使用引用时,您可以使用一种迭代器(例如List.iterator()和List.listIterator()来显式地使用一个引用,尽管在大多数情况下它们返回相同的实现)。您还可以在循环中引用迭代器。这允许您从集合中删除项目,而不获得ConcurrentModificationException。

e.g.

This is ok:

这是好的:

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();
     }
}

This is not, as it will throw a concurrent modification 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);
     }
}

#3


20  

To expand on Paul's own answer, he has demonstrated that the bytecode is the same on that particular compiler (presumably Sun's javac?) but different compilers are not guaranteed to generate the same bytecode, right? To see what the actual difference is between the two, let's go straight to the source and check the Java Language Specification, specifically 14.14.2, "The enhanced for statement":

为了扩展Paul自己的答案,他证明了字节码在特定的编译器上是相同的(想必是Sun的javac?)但是不同的编译器不能保证生成相同的字节码,对吧?为了了解两者之间的实际区别,让我们直接访问源代码并检查Java语言规范,特别是14.14.2,“增强的for语句”:

The enhanced for statement is equivalent to a basic for statement of the form:

增补的for语句相当于该表单的basic for语句:

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

In other words, it is required by the JLS that the two are equivalent. In theory that could mean marginal differences in bytecode, but in reality the enhanced for loop is required to:

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

  • Invoke the .iterator() method
  • 调用.iterator()方法
  • Use .hasNext()
  • 使用.hasNext()
  • Make the local variable available via .next()
  • 通过.next()使局部变量可用

So, in other words, for all practical purposes the bytecode will be identical, or nearly-identical. It's hard to envisage any compiler implementation which would result in any significant difference between the two.

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

#4


1  

You might need to use iterators if you need to modify collection in your loop. First approach will throw 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
}

#5


0  

Iterator is an interface in the Java Collections framework that provides methods to traverse or iterate over a collection.

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

Both iterator and for loop acts similar when your motive is to just traverse over a collection to read its elements.

当您的动机只是遍历一个集合来读取它的元素时,iterator和for循环的行为都是类似的。

for-each is just one way to iterate over the Collection.

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

For example:

例如:

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);
}

And for-each loop can be used only on objects implementing the iterator interface.

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

Now back to the case of for loop and iterator.

现在回到for循环和迭代器的例子。

The difference comes when you try to modify a collection. In this case, iterator is more efficient because of its fail-fast property. ie. it checks for any modification in the structure of underlying collection before iterating over the next element. If there are any modifications found, it will throw the ConcurrentModificationException.

当您试图修改集合时,就会产生差异。在这种情况下,迭代器更高效,因为它具有快速故障属性。ie。它在迭代下一个元素之前检查底层集合结构中的任何修改。如果发现任何修改,它将抛出ConcurrentModificationException。

(Note: This functionality of iterator is only applicable in case of collection classes in java.util package. It is not applicable for concurrent collections as they are fail-safe by nature)

(注意:iterator的这个功能只适用于java中的集合类。util包。它不适用于并发集合,因为它们本质上是故障安全的)

#6


0  

foreach uses iterators under the hood anyway. It really is just syntactic sugar.

foreach在hood下面使用迭代器。只是语法上的糖。

Consider the following program:

考虑以下项目:

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) {
        }
    }
}

Let's compile it with javac Whatever.java,
And read the disassembled bytecode of main(), using javap -c Whatever:

让我们用javac来编译它。java,并使用java -c读取main()分解后的字节码:

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

We can see that foreach compiles down to a program which:

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

  • Creates iterator using List.iterator()
  • 使用List.iterator创建迭代器()
  • If Iterator.hasNext(): invokes Iterator.next() and continues loop
  • 如果Iterator.hasNext():调用Iterator.next()并继续循环

As for "why doesn't this useless loop get optimized out of the compiled code? we can see that it doesn't do anything with the list item": well, it's possible for you to code your iterable such that .iterator() has side-effects, or so that .hasNext() has side-effects or meaningful consequences.

至于“为什么这个无用的循环不能从编译后的代码中得到优化?”我们可以看到它对列表项没有任何作用:“嗯,您可以对iterable进行编码,例如。iterator()有副作用,或者是这样。hasnext()有副作用或有意义的结果。

You could easily imagine that an iterable representing a scrollable query from a database might do something dramatic on .hasNext() (like contacting the database, or closing a cursor because you've reached the end of the result set).

您可以很容易地想象,表示来自数据库的可滚动查询的可迭代程序可能会在. hasnext()上做一些引人注目的事情(比如联系数据库,或者关闭游标,因为您已经到达结果集的末尾)。

So, even though we can prove that nothing happens in the loop body… it is more expensive (intractable?) to prove that nothing meaningful/consequential happens when we iterate. The compiler has to leave this empty loop body in the program.

因此,尽管我们可以证明在循环体中没有发生任何事情……但证明在迭代时没有发生任何有意义的/重要的事情要花费更大的代价(难以处理?)编译器必须将这个空循环体留在程序中。

The best we could hope for would be a compiler warning. It's interesting that javac -Xlint:all Whatever.java does not warn us about this empty loop body. IntelliJ IDEA does though. Admittedly I have configured IntelliJ to use Eclipse Compiler, but that may not be the reason why.

我们所能期望的最好结果就是一个编译器警告。有趣的是,javac -Xlint:随便什么。java没有警告我们这个空循环体。IntelliJ IDEA虽然。无可否认,我已经配置IntelliJ来使用Eclipse编译器,但这可能不是原因。

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

#7


0  

The foreach underhood is creating the iterator, calling hasNext() and calling next() to get the value; The issue with the performance comes only if you are using something that implements the RandomomAccess.

foreach底层框架正在创建迭代器,调用hasNext()并调用next()来获取值;只有在使用实现RandomomAccess的东西时,性能才会出现问题。

for (Iterator<CustomObj> iter = customList.iterator(); iter.hasNext()){
   CustomObj custObj = iter.next();
   ....
}

Performance issues with the iterator-based loop is because it is:

基于迭代器的循环的性能问题是因为:

  1. allocating an object even if the list is empty (Iterator<CustomObj> iter = customList.iterator(););
  2. 分配一个对象,即使列表是空的(迭代器 iter = customList.iterator(););
  3. iter.hasNext() during every iteration of the loop there is an invokeInterface virtual call (go through all the classes, then do method table lookup before the jump).
  4. 在循环的每次迭代中都有一个invokeInterface虚拟调用(遍历所有类,然后在跳转之前进行方法表查找)。
  5. the implementation of the iterator has to do at least 2 fields lookup in order to make hasNext() call figure the value: #1 get current count and #2 get total count
  6. 迭代器的实现必须至少进行两个字段查找,以便使hasNext()调用值:#1获得当前计数,#2获得总数。
  7. inside the body loop, there is another invokeInterface virtual call iter.next(so: go through all the classes and do method table lookup before the jump) and as well has to do fields lookup: #1 get the index and #2 get the reference to the array to do the offset into it (in every iteration).
  8. 在body循环中,还有另一个invokeInterface虚拟调用iter。接下来(因此:在跳转之前遍历所有类并进行方法表查找),还必须进行字段查找:#1获取索引,#2获取对数组的引用,以在每次迭代中对数组进行偏移。

A potential optimiziation is to switch to an index iteration with the cached size lookup:

一个潜在的优化是使用缓存大小查找切换到索引迭代:

for(int x = 0, size = customList.size(); x < size; x++){
  CustomObj custObj = customList.get(x);
  ...
}

Here we have:

这里我们有:

  1. one invokeInterface virtual method call customList.size() on the initial creation of the for loop to get the size
  2. 一个invokeInterface虚拟方法调用customList.size()在for循环的初始创建中获取大小。
  3. the get method call customList.get(x) during the body for loop, which is a field lookup to the array and then can do the offset into the array
  4. get方法在body for循环期间调用customList.get(x),这是对数组的字段查找,然后可以对数组进行偏移

We reduced a ton of method calls, field lookups. This you don't want to do with LinkedList or with something that is not a RandomAccess collection obj, otherwise the customList.get(x) is gonna turn into something that has to traverse the LinkedList on every iteration.

我们减少了大量的方法调用,字段查找。你不希望对LinkedList或非随机性访问集合obj进行处理,否则的话,customList.get(x)会变成在每次迭代中都要遍历LinkedList的东西。

This is perfect when you know that is any RandomAccess based list collection.

当您知道这是任何基于随机访问的列表集合时,这是完美的。

#8


-8  

We should avoid using traditional for loop while working with Collections. The simple reason what I will give is that the complexity of for loop is of the order O(sqr(n)) and complexity of Iterator or even the enhanced for loop is just O(n). So it gives a performence difference.. Just take a list of some 1000 items and print it using both ways. and also print the time difference for the execution. You can sees the difference.

在处理集合时,我们应该避免使用传统的for循环。我要给出的简单原因是for循环的复杂度是O(sqr(n)),而Iterator的复杂度,甚至是增强的for循环的复杂度是O(n)。所以这就产生了行为差异。只需要列出1000个条目,然后用两种方法打印出来。并打印执行时的时差。你可以看到区别。

#1


245  

If you are just wandering over the collection to read all of the values, then there is no difference between using an iterator or the new for loop syntax, as the new syntax just uses the iterator underwater.

如果您只是浏览集合以读取所有的值,那么使用迭代器或新的for循环语法没有区别,因为新的语法只是在水下使用迭代器。

If however, you mean by loop the old "c-style" loop:

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

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

Then the new for loop, or iterator, can be a lot more efficient, depending on the underlying data structure. The reason for this is that for some data structures, get(i) is an O(n) operation, which makes the loop an O(n2) operation. A traditional linked list is an example of such a data structure. All iterators have as a fundamental requirement that next() should be an O(1) operation, making the loop O(n).

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

To verify that the iterator is used underwater by the new for loop syntax, compare the generated bytecodes from the following two Java snippets. First the for loop:

为了验证新的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

And second, the iterator:

第二,迭代器:

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

As you can see, the generated byte code is effectively identical, so there is no performance penalty to using either form. Therefore, you should choose the form of loop that is most aesthetically appealing to you, for most people that will be the for-each loop, as that has less boilerplate code.

如您所见,生成的字节代码实际上是相同的,因此使用任何一种形式都不会造成性能损失。因此,您应该选择最美观的循环形式,对于大多数人来说,它是for-each循环,因为它的样板代码更少。

#2


95  

The difference isn't in performance, but in capability. When using a reference directly you have more power over explicitly using a type of iterator (e.g. List.iterator() vs. List.listIterator(), although in most cases they return the same implementation). You also have the ability to reference the Iterator in your loop. This allows you to do things like remove items from your collection without getting a ConcurrentModificationException.

区别不在于性能,而在于能力。当直接使用引用时,您可以使用一种迭代器(例如List.iterator()和List.listIterator()来显式地使用一个引用,尽管在大多数情况下它们返回相同的实现)。您还可以在循环中引用迭代器。这允许您从集合中删除项目,而不获得ConcurrentModificationException。

e.g.

This is ok:

这是好的:

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();
     }
}

This is not, as it will throw a concurrent modification 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);
     }
}

#3


20  

To expand on Paul's own answer, he has demonstrated that the bytecode is the same on that particular compiler (presumably Sun's javac?) but different compilers are not guaranteed to generate the same bytecode, right? To see what the actual difference is between the two, let's go straight to the source and check the Java Language Specification, specifically 14.14.2, "The enhanced for statement":

为了扩展Paul自己的答案,他证明了字节码在特定的编译器上是相同的(想必是Sun的javac?)但是不同的编译器不能保证生成相同的字节码,对吧?为了了解两者之间的实际区别,让我们直接访问源代码并检查Java语言规范,特别是14.14.2,“增强的for语句”:

The enhanced for statement is equivalent to a basic for statement of the form:

增补的for语句相当于该表单的basic for语句:

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

In other words, it is required by the JLS that the two are equivalent. In theory that could mean marginal differences in bytecode, but in reality the enhanced for loop is required to:

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

  • Invoke the .iterator() method
  • 调用.iterator()方法
  • Use .hasNext()
  • 使用.hasNext()
  • Make the local variable available via .next()
  • 通过.next()使局部变量可用

So, in other words, for all practical purposes the bytecode will be identical, or nearly-identical. It's hard to envisage any compiler implementation which would result in any significant difference between the two.

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

#4


1  

You might need to use iterators if you need to modify collection in your loop. First approach will throw 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
}

#5


0  

Iterator is an interface in the Java Collections framework that provides methods to traverse or iterate over a collection.

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

Both iterator and for loop acts similar when your motive is to just traverse over a collection to read its elements.

当您的动机只是遍历一个集合来读取它的元素时,iterator和for循环的行为都是类似的。

for-each is just one way to iterate over the Collection.

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

For example:

例如:

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);
}

And for-each loop can be used only on objects implementing the iterator interface.

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

Now back to the case of for loop and iterator.

现在回到for循环和迭代器的例子。

The difference comes when you try to modify a collection. In this case, iterator is more efficient because of its fail-fast property. ie. it checks for any modification in the structure of underlying collection before iterating over the next element. If there are any modifications found, it will throw the ConcurrentModificationException.

当您试图修改集合时,就会产生差异。在这种情况下,迭代器更高效,因为它具有快速故障属性。ie。它在迭代下一个元素之前检查底层集合结构中的任何修改。如果发现任何修改,它将抛出ConcurrentModificationException。

(Note: This functionality of iterator is only applicable in case of collection classes in java.util package. It is not applicable for concurrent collections as they are fail-safe by nature)

(注意:iterator的这个功能只适用于java中的集合类。util包。它不适用于并发集合,因为它们本质上是故障安全的)

#6


0  

foreach uses iterators under the hood anyway. It really is just syntactic sugar.

foreach在hood下面使用迭代器。只是语法上的糖。

Consider the following program:

考虑以下项目:

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) {
        }
    }
}

Let's compile it with javac Whatever.java,
And read the disassembled bytecode of main(), using javap -c Whatever:

让我们用javac来编译它。java,并使用java -c读取main()分解后的字节码:

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

We can see that foreach compiles down to a program which:

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

  • Creates iterator using List.iterator()
  • 使用List.iterator创建迭代器()
  • If Iterator.hasNext(): invokes Iterator.next() and continues loop
  • 如果Iterator.hasNext():调用Iterator.next()并继续循环

As for "why doesn't this useless loop get optimized out of the compiled code? we can see that it doesn't do anything with the list item": well, it's possible for you to code your iterable such that .iterator() has side-effects, or so that .hasNext() has side-effects or meaningful consequences.

至于“为什么这个无用的循环不能从编译后的代码中得到优化?”我们可以看到它对列表项没有任何作用:“嗯,您可以对iterable进行编码,例如。iterator()有副作用,或者是这样。hasnext()有副作用或有意义的结果。

You could easily imagine that an iterable representing a scrollable query from a database might do something dramatic on .hasNext() (like contacting the database, or closing a cursor because you've reached the end of the result set).

您可以很容易地想象,表示来自数据库的可滚动查询的可迭代程序可能会在. hasnext()上做一些引人注目的事情(比如联系数据库,或者关闭游标,因为您已经到达结果集的末尾)。

So, even though we can prove that nothing happens in the loop body… it is more expensive (intractable?) to prove that nothing meaningful/consequential happens when we iterate. The compiler has to leave this empty loop body in the program.

因此,尽管我们可以证明在循环体中没有发生任何事情……但证明在迭代时没有发生任何有意义的/重要的事情要花费更大的代价(难以处理?)编译器必须将这个空循环体留在程序中。

The best we could hope for would be a compiler warning. It's interesting that javac -Xlint:all Whatever.java does not warn us about this empty loop body. IntelliJ IDEA does though. Admittedly I have configured IntelliJ to use Eclipse Compiler, but that may not be the reason why.

我们所能期望的最好结果就是一个编译器警告。有趣的是,javac -Xlint:随便什么。java没有警告我们这个空循环体。IntelliJ IDEA虽然。无可否认,我已经配置IntelliJ来使用Eclipse编译器,但这可能不是原因。

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

#7


0  

The foreach underhood is creating the iterator, calling hasNext() and calling next() to get the value; The issue with the performance comes only if you are using something that implements the RandomomAccess.

foreach底层框架正在创建迭代器,调用hasNext()并调用next()来获取值;只有在使用实现RandomomAccess的东西时,性能才会出现问题。

for (Iterator<CustomObj> iter = customList.iterator(); iter.hasNext()){
   CustomObj custObj = iter.next();
   ....
}

Performance issues with the iterator-based loop is because it is:

基于迭代器的循环的性能问题是因为:

  1. allocating an object even if the list is empty (Iterator<CustomObj> iter = customList.iterator(););
  2. 分配一个对象,即使列表是空的(迭代器 iter = customList.iterator(););
  3. iter.hasNext() during every iteration of the loop there is an invokeInterface virtual call (go through all the classes, then do method table lookup before the jump).
  4. 在循环的每次迭代中都有一个invokeInterface虚拟调用(遍历所有类,然后在跳转之前进行方法表查找)。
  5. the implementation of the iterator has to do at least 2 fields lookup in order to make hasNext() call figure the value: #1 get current count and #2 get total count
  6. 迭代器的实现必须至少进行两个字段查找,以便使hasNext()调用值:#1获得当前计数,#2获得总数。
  7. inside the body loop, there is another invokeInterface virtual call iter.next(so: go through all the classes and do method table lookup before the jump) and as well has to do fields lookup: #1 get the index and #2 get the reference to the array to do the offset into it (in every iteration).
  8. 在body循环中,还有另一个invokeInterface虚拟调用iter。接下来(因此:在跳转之前遍历所有类并进行方法表查找),还必须进行字段查找:#1获取索引,#2获取对数组的引用,以在每次迭代中对数组进行偏移。

A potential optimiziation is to switch to an index iteration with the cached size lookup:

一个潜在的优化是使用缓存大小查找切换到索引迭代:

for(int x = 0, size = customList.size(); x < size; x++){
  CustomObj custObj = customList.get(x);
  ...
}

Here we have:

这里我们有:

  1. one invokeInterface virtual method call customList.size() on the initial creation of the for loop to get the size
  2. 一个invokeInterface虚拟方法调用customList.size()在for循环的初始创建中获取大小。
  3. the get method call customList.get(x) during the body for loop, which is a field lookup to the array and then can do the offset into the array
  4. get方法在body for循环期间调用customList.get(x),这是对数组的字段查找,然后可以对数组进行偏移

We reduced a ton of method calls, field lookups. This you don't want to do with LinkedList or with something that is not a RandomAccess collection obj, otherwise the customList.get(x) is gonna turn into something that has to traverse the LinkedList on every iteration.

我们减少了大量的方法调用,字段查找。你不希望对LinkedList或非随机性访问集合obj进行处理,否则的话,customList.get(x)会变成在每次迭代中都要遍历LinkedList的东西。

This is perfect when you know that is any RandomAccess based list collection.

当您知道这是任何基于随机访问的列表集合时,这是完美的。

#8


-8  

We should avoid using traditional for loop while working with Collections. The simple reason what I will give is that the complexity of for loop is of the order O(sqr(n)) and complexity of Iterator or even the enhanced for loop is just O(n). So it gives a performence difference.. Just take a list of some 1000 items and print it using both ways. and also print the time difference for the execution. You can sees the difference.

在处理集合时,我们应该避免使用传统的for循环。我要给出的简单原因是for循环的复杂度是O(sqr(n)),而Iterator的复杂度,甚至是增强的for循环的复杂度是O(n)。所以这就产生了行为差异。只需要列出1000个条目,然后用两种方法打印出来。并打印执行时的时差。你可以看到区别。