【问题标题】:why is the enhanced for loop more efficient than the normal for loop为什么增强的for循环比普通的for循环更有效
【发布时间】:2012-07-18 07:37:20
【问题描述】:

我在这里读到增强的for循环比普通的for循环效率更高:

http://developer.android.com/guide/practices/performance.html#foreach

当我搜索它们的效率差异时,我发现的是:在正常的for循环的情况下,我们需要一个额外的步骤来找出数组的长度或大小等,

for(Integer i : list){
   ....
}


int n = list.size();
for(int i=0; i < n; ++i){
  ....
}

但这是唯一的原因吗,增强的 for 循环比普通的 for 循环好?在这种情况下,最好使用普通的 for 循环,因为理解增强的 for 循环会稍微复杂一些。

检查这个有趣的问题:http://www.coderanch.com/t/258147/java-programmer-SCJP/certification/Enhanced-Loop-Vs-Loop

谁能解释一下这两种for循环的内部实现,或者解释一下使用增强的for循环的其他原因吗?

【问题讨论】:

  • 如果你在某处“读到过一些东西”,最好尽可能引用它。
  • 我在这里读到...但没有任何解释为什么会这样:developer.android.com/guide/practices/performance.html#foreach
  • “总结一下:默认使用增强的 for 循环,但考虑使用手写计数循环来进行性能关键的 ArrayList 迭代。”这与您阅读的内容相反:对于 ArrayList,第二种选择更快。
  • @Archie.bpgc:实际上,is 有一个解释:“zero() 最慢,因为 JIT 还无法优化获取数组长度的成本每次循环迭代一次。”。这对我来说似乎是一种解释。
  • 留下 zero()...这是最糟糕的循环方式...但是 one() 呢?我的问题是 one() 和 two() 在遍历数组时没有区别(在 java 的情况下)......但在 android 中我更好地使用 two() 甚至数组??

标签: java for-loop foreach


【解决方案1】:

我读到增强的 for 循环比普通的 for 循环更有效。

实际上有时它对程序的效率较低,但大多数时候它完全一样。

它对开发人员来说更高效,这通常更为重要

for-each 循环在遍历集合时特别有用。

List<String> list = 
for(Iterator<String> iter = list.iterator(); list.hasNext(); ) {
    String s = list.next();

更容易写成(但做同样的事情,所以它对程序没有效率)

List<String> list = 
for(String s: list) {

在通过索引访问随机访问的集合时,使用“旧”循环的效率略高。

List<String> list = new ArrayList<String>(); // implements RandomAccess
for(int i=0, len = list.size(); i < len; i++) // doesn't use an Iterator!

在集合上使用 for-each 循环总是使用迭代器,这对于随机访问列表的效率略低。

AFAIK,对于程序来说,使用 for-each 循环永远不会更有效,但就像我说的那样,开发人员的效率通常更重要

【讨论】:

  • 您能否解释一下实现(它们的不同之处)或其中一个比另一个更有效的案例示例??
  • 正如其他人在上面指出的那样,写得不好的老式 for 循环可能不如新的 for-each 效率低。所以现在你知道了 for-each 循环对程序更有效的情况。
  • @UncleIroh 使用 for-each 循环意味着您不太可能编写低效的东西。但是,如果您知道自己在做什么,那么 for-each 生成的字节码与您可能自己编写的字节码完全相同,但只是简写。从字节码级别来看,没有办法确定区别(期望生成的局部变量名称是赠品)。编写良好的代码在这两种情况下都是相同的。如果您有一个 ArrayList,则索引 for 循环比使用 for-each 更有效,后者使用迭代器。
【解决方案2】:

for-each 使用 Iterator 接口。我怀疑它是否比“旧”样式更有效。迭代器还需要检查列表的大小。

主要是为了可读性。

对于像 LinkedList 这样的非随机访问集合应该更快,但是这样比较是不公平的。无论如何,您不会习惯于第二次实现(索引访问速度较慢)。

【讨论】:

    【解决方案3】:

    说增强的 for 循环效率更高,有点过于简单化了。 可以,但在许多情况下,它几乎与老式循环完全相同。

    首先要注意的是,对于集合,增强的 for 循环使用 Iterator,因此如果您使用 Iterator 手动迭代集合,那么您应该具有与增强的 for 循环几乎相同的性能。

    增强的for循环比简单实现传统循环更快的地方是这样的:

    LinkedList<Object> list = ...;
    
    // Loop 1:
    int size = list.size();
    for (int i = 0; i<size; i++) {
       Object o = list.get(i);
       /// do stuff
    }
    
    // Loop 2:
    for (Object o : list) {
      // do stuff
    }
    
    // Loop 3:
    Iterator<Object> it = list.iterator();
    while (it.hasNext()) {
      Object o = it.next();
      // do stuff
    }
    

    在这种情况下,循环 1 将比循环 2 和循环 3 慢,因为它必须(部分)在每次迭代中遍历列表以找到位置 i 处的元素。然而,由于使用了Iterator,循环 2 和 3 只会在列表中再向前移动一个元素。循环 2 和 3 也将具有几乎相同的性能,因为循环 3 几乎正是您在循环 2 中编写代码时编译器将产生的结果。

    【讨论】:

    • @Joachim,区别最终似乎是引用示例中集合的随机访问和串行访问。对于数组,两种循环类型的行为相同。
    • 但是这里的链接developer.android.com/guide/practices/…给出了一个数组的例子......所以根据你的解释我们不需要在遍历数组时使用foreach吗??
    • @Archie.bpgc:首先:Android 不同于“Java”。 Dalvik VM(还)在优化方面不如 HotSpot VM,因此在某些地方,Android 的性能建议与“普通”Java 不同。这是一个这样的案例。仔细阅读文档:尤其是示例 onetwothree 在“正常”JVM 上的性能可能非常相似。
    • 但在 android 中我更好地使用第三种方法,即使是数组??
    • @Archie.bpgc:是的,对于 Android,three 是数组的最佳示例。对于ArrayList(可能还有其他随机访问List 实现),本指南告诉您不要使用增强的for 循环(可能是为了避免Iterator 分配开销)。跨度>
    【解决方案4】:

    foreach 循环和这种循环一样高效:

    for (Iterator<Foo> it = list.iterator(); it.hasNext(); ) {
         Foo foo = it.next();
         ...
    }
    

    因为它是严格等价的。

    如果你遍历一个列表使用

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

    然后 foreach 循环将具有与您的循环相同的性能,但仅适用于 ArrayList。在 LinkedList 的情况下,您的循环将具有糟糕的性能,因为在每次迭代时,它都必须遍历列表的所有节点,直到到达 ith 元素。

    foreach 循环(或基于迭代器的循环,同上)没有这个问题,因为迭代器保持对当前节点的引用,并在每次迭代时简单地转到下一个节点。这是最糟糕的选择,因为它适用于所有类型的列表。它还更清楚地表达了意图,并且更安全,因为您不会冒险增加循环内的索引,或者在嵌套循环的情况下使用错误的索引。

    【讨论】:

    • 你的意思是它们具有相同的效率,但是当在链表或类似的东西上使用循环时......正常的 for 循环递归遍历,而 foreach 循环迭代遍历??
    • 它不是递归与迭代。只是链表上的get() 操作包括从第一个节点开始,并获取下一个直到索引i。在 ArrayList 的情况下,这是一个 O(n) 操作,而不是 O(1)。从迭代器中获取下一个元素总是 O(1)。列表是 ArrayList 还是 LinkedList 无关紧要:性能尽可能好。
    • 如果您正在使用LinkedList(密集),那么您的性能可能很差。
    【解决方案5】:

    来自有效的 Java:

    循环遍历数组的标准习语不一定会导致 多余的检查。现代 JVM 实现将它们优化掉。

    但 Josh Bloch 没有描述 JVM 如何优化它们。

    【讨论】:

    • 他当然没有!它是一本关于如何编写好的 Java 代码的教科书,而不是关于 JVM 内部的教科书。要了解优化器如何做某事,您需要查看有关编译器编写的教科书。有关 Java JIT 编译器如何执行此操作的详细信息,请查看 JVM 源代码。
    【解决方案6】:

    我自己对我今天用上述几点进行的一个小实验感到惊讶。 所以我所做的是我将一定数量的元素插入到链表中,并使用上述三种方法对其进行迭代 1) 使用高级 for 循环 2) 使用迭代器 3) 使用简单循环和 get()
    我想像你们这样的程序员通过看到代码可以更好地理解我所做的。

    long advanced_timeElapsed,iterating_timeElapsed,simple_timeElapsed;
        long first=System.nanoTime();
        for(Integer i: myList){
            Integer b=i;
        }
        long end= System.nanoTime();
        advanced_timeElapsed=end-first;
        System.out.println("Time for Advanced for loop:"+advanced_timeElapsed);
        first=System.nanoTime();
        Iterator<Integer> it = myList.iterator();
        while(it.hasNext())
        {
            Integer b=it.next();
        }
        end= System.nanoTime();
        iterating_timeElapsed=end-first;
        System.out.println("Time for Iterating Loop:"+iterating_timeElapsed);
        first=System.nanoTime();
        int counter=0;
        int size= myList.size();
        while(counter<size)
        {
            Integer b=myList.get(counter);
            counter++;  
        }
        end= System.nanoTime();
        simple_timeElapsed=end-first;
        System.out.println("Time for Simple Loop:"+simple_timeElapsed);
    

    结果出乎我的意料。以下是 3 种情况下经过的时间图。

    Y 轴时间经过 X轴-测试用例
    测试用例 1:10 输入
    测试用例 2:30 输入
    测试用例 3:50 输入
    测试用例 4:100 个输入
    测试用例 5:150 个输入
    测试用例6:300 个输入
    测试用例 7:500 个输入
    测试用例 8:1000 个输入
    测试用例 9:2000 个输入
    测试用例10:5000 个输入
    测试用例11:10000 个输入
    测试用例 12:100000 个输入

    在这里您可以看到简单循环的执行方式比其他循环更好。如果您在上面的代码中发现任何错误,请回复,我会再次检查。在我挖掘字节码并查看引擎盖下发生的事情后,将进一步更新。 对于这么长的回复,我深表歉意,但我喜欢描述性的。 菲利普

    【讨论】:

    • 所以它归结为这种情况1)高级for循环很简单,并且实现了迭代器。它创建一个迭代器对象。执行 hasNext() 并执行迭代器的 next() 以获取下一个元素。所以实际上它与案例 2 相同。但它并不能解释图形 b/w 高级和迭代器中的急剧攀升。情况 3 的工作效率更高,因为在 arraylist 中,get(index) 是对数组的随机访问。所以它应该表现良好
    • 你有没有重新排序测试用例?第一个实现速度较慢的原因可能是由于缓存未命中(对象尚未在缓存中)...
    • 是的,事实上,我重新订购并尝试过。我不知道 JIT 是否在搞乱基准。但是当我查看字节码时,它确实表明简单循环效果更好。
    • 这取决于数据结构。如果你使用 LinkedList,你会看到完全不同的画面。
    【解决方案7】:

    所有答案都很好,我认为不需要更多答案,但我只想指出:

    增强的for语句只能用于obtain array elements

    不能使用modify elements

    如果您的程序需要修改元素,请使用传统的counter-controlled for 语句。

    看看

    int[] array = { 1, 2, 3, 4, 5 };
    for (int counter = 0; counter < array.length; counter++)
        array[counter] *= 2;
    

    我们不能使用enhanced for 语句,因为我们是modifying the array’s elements

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-23
      • 1970-01-01
      • 2016-04-15
      • 2021-10-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      相关资源
      最近更新 更多