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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2113216/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 03:40:00  来源:igfitidea点击:

Which is more efficient, a for-each loop, or an iterator?

javacollectionsforeach

提问by Paul Wagland

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.

请注意,这不是thisthisthisthis的完全重复,尽管最后一个问题的答案很接近。这不是一个骗局的原因是,其中大多数是比较循环get(i)内部调用的循环,而不是使用迭代器。

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

正如Meta上所建议的,我将发布我对这个问题的回答。

采纳答案by Paul Wagland

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(n 2) 操作。传统的链表就是这种数据结构的一个例子。所有迭代器的基本要求都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 循环,因为它具有较少的样板代码。

回答by Michael Krauklis

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

回答by Cowan

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 guaranteedto 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 forstatement is equivalent to a basic forstatement of the form:

增强for语句等效于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 要求两者等价。理论上,这可能意味着字节码的微小差异,但实际上,增强的 for 循环需要:

  • Invoke the .iterator()method
  • Use .hasNext()
  • Make the local variable available via .next()
  • 调用.iterator()方法
  • .hasNext()
  • 使局部变量可用 .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.

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

回答by Chandan

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 个项目并使用两种方式打印它。并打印执行的时间差。你可以看到不同之处。

回答by eccentricCoder

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

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

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

当您的动机只是遍历集合以读取其元素时,迭代器和 for 循环的行为相似。

for-eachis 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 循环只能用于实现迭代器接口的对象。

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)

(注意:迭代器的此功能仅适用于 java.util 包中的集合类。不适用于并发集合,因为它们本质上是故障安全的)

回答by Birchlabs

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

foreach无论如何都在幕后使用迭代器。它真的只是语法糖。

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 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

We can see that foreachcompiles down to a program which:

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

  • Creates iterator using List.iterator()
  • If Iterator.hasNext(): invokes Iterator.next()and continues loop
  • 使用创建迭代器 List.iterator()
  • 如果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.

至于“为什么这个无用的循环没有从编译后的代码中得到优化?我们可以看到它对列表项没有做任何事情”:好吧,您可以编写.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.javadoes notwarn 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:all Whatever.java没有警告我们这个空的循环体。IntelliJ IDEA 确实如此。诚然,我已将 IntelliJ 配置为使用 Eclipse 编译器,但这可能不是原因。

enter image description here

在此处输入图片说明

回答by denis_lor

The foreachunderhood 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发动机罩被创建iterator,调用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.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).
  3. 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
  4. 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).
  1. 即使列表为空,也分配一个对象(Iterator<CustomObj> iter = customList.iterator(););
  2. iter.hasNext()在循环的每次迭代期间,都会有一个 invokeInterface 虚拟调用(遍历所有类,然后在跳转之前进行方法表查找)。
  3. 迭代器的实现必须至少查找 2 个字段才能使hasNext()调用数字成为值:#1 获取当前计数和 #2 获取总计数
  4. 在主体循环内部,还有另一个 invokeInterface 虚拟调用iter.next(因此:在跳转之前遍历所有类并进行方法表查找)并且还必须进行字段查找:#1 获取索引,#2 获取对数组对其进行偏移(在每次迭代中)。

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

一个潜在的优化是切换到index iteration缓存大小查找:

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. 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
  1. customList.size()在初始创建 for 循环时调用一个 invokeInterface 虚拟方法来获取大小
  2. customList.get(x)for 循环体期间调用 get 方法,这是对数组的字段查找,然后可以将偏移量放入数组中

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

我们减少了大量的方法调用和字段查找。您不想使用LinkedList或使用不是RandomAccess集合 obj 的东西,否则customList.get(x)它将变成必须LinkedList在每次迭代中遍历的东西。

This is perfect when you know that is any RandomAccessbased list collection.

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