【问题标题】:Java - Difference between for loop terminating expressionJava - for循环终止表达式之间的区别
【发布时间】:2011-07-04 19:44:25
【问题描述】:

我只是好奇:这两个循环实现在速度和性能上是否存在差异?假设 size() 方法返回处理一组元素的数组、集合或对象的长度(实际上是来自 XOM api)。

实施 1:

int size = someArray.size();
for (int i = 0; i < size; i++) {
    // do stuff here
}

实施 2:

for (int i = 0; i < someArray.size(); i++) {
    // do stuff here
}

【问题讨论】:

    标签: java loops performance xom


    【解决方案1】:

    始终避免任何可以在循环之外完成的事情,例如方法调用、为变量赋值或测试条件。

    方法调用比没有调用的等效代码成本更高,并且通过一次又一次地重复方法调用,您只会增加应用程序的开销。

    将任何方法调用移出循环,即使这需要重写代码。

    好处:-

    除非编译器对其进行优化,否则将为循环中的每次迭代计算循环条件。

    如果条件值不会改变,如果方法调用移出循环,代码将执行得更快。

    注意:-

    如果方法返回的值在循环期间不会改变,则将其值存储在循环前的临时变量中。

    因此它的值存储在循环外的一个临时变量大小中,然后用作循环终止条件。

    【讨论】:

      【解决方案2】:

      也许值得注意的是这个结构:

      for (String s : getStringsList()) {
          //...
      }
      

      只调用一次getStringsList(),然后在后台对迭代器进行操作。因此,在getStringsList() 中执行冗长的操作或更改某些状态是安全的。

      【讨论】:

      • 因此,在 getStringsList() 中执行冗长的操作或更改某些状态是安全的。 如果发生这种情况,最有可能的是 ConcurrentModificationException 在线(erm 堆栈)
      • 我不太明白。我的意思是 getStringsList() 可能是一种昂贵的方法(例如来自 DAO),因为它在循环开始之前只被调用一次。之后 for 循环在返回的列表上运行,或者更准确地说,在迭代器上运行。 ConcurrentModificationException的地址在哪里?我假设 getStringsList() 不包含对返回列表的引用。
      【解决方案3】:

      别担心,现在 JVM 优化非常激进。

      使用第二种形式,因为它更具可读性,而且很可能也一样快。过早优化yada yada。

      当您确实需要提高速度时,请始终先配置文件,不要猜测。

      在局部变量中缓存 size() 不太可能使您的应用程序显着受益。如果是这样,您必须对庞大的数据集进行简单的操作。在这种情况下,您根本不应该使用 ArrayList。

      【讨论】:

        【解决方案4】:

        是的,有区别。在第一个循环中,size() 方法只被调用一次。在第二个中,它在每次迭代时被调用。

        如果迭代修改了集合的大小(这非常罕见),则需要第二个。在大多数情况下,您应该更喜欢第一个,但要限制 size 变量的范围:

        for (int i = 0, size = someArray.size(); i < size; i++) {
            // ...
        }
        

        但大多数时候,你应该更喜欢 foreach 语法:

        for (Foo foo : collection) {
            // ...
        }
        

        这将有效地迭代数组或集合,即使是对于索引访问不是最佳的 LinkedList。

        【讨论】:

        • 我想我不能在我的情况下使用 foreach 。 XOM API 的元素特性不能以这种方式遍历。 (XOM 是 Java 的 XML 处理库)
        • 如果在迭代过程中大小会发生变化,那么可能存在非常严重的设计/同步问题。此外,在这样的循环中,很可能会有一个 get(i) 并且如果大小发生变化,它可能会发生变化,例如,在重新读取大小之后但是做get之前,在这种情况下代码会抛出异常。
        • @SyntaxT3rr0r:同意。我正在考虑一个循环,它在迭代内从集合中添加/删除项目(无多线程)。但我同意这是非常非常罕见的。我将编辑我的答案以使其更清晰。
        • 如果您在循环期间添加/删除,请确保您相应地更改大小变量。简单的解决方案是size=collection.size(),其他包括size++或者size--等等。
        【解决方案5】:

        从性能的角度来看,差别不大。这是因为可以优化循环,以便内联 size() 查找,从而产生非常小的性能差异。

        主要区别在于循环时大小是否发生变化。第一种情况将尝试迭代固定次数。在第二种情况下,迭代次数将取决于最终的 size()。

        【讨论】:

          【解决方案6】:

          第一个 sn-p 必然会执行得更快,因为它只调用一次 size()。第二次 sn-p 调用size() N 次。取决于实现。它可能会造成重大处罚,尤其是。如果编译器发现很难内联该方法和/或size() 方法不只是返回非易失性变量等。

          我会像for(int i=0, s=someCollection.size(); i&lt;s; i++)一样重写它

          注意:数组没有size() 方法。

          【讨论】:

          • @bestss 这就是为什么我一般指定它(对于元素组):)
          • @Jairo,数组和集合之间存在巨大差异。数组在任何情况下都不能改变它们的大小。 for(int i=0;i&lt;array.length;i++) 比集合遍历对性能更友好。 JVM 不会为此类循环添加边界检查代码(仅一次)。
          • @bestsss:ArrayList 确实有一个 size() 方法,并且不知何故是一个数组;)
          • @SyntaxT3rr0r,ArrayList 不是数组。它是一个包装器,它可以通过分配一个新数组来改变它的大小(不像数组)。这就是为什么你不能总是优化 ArrayList 的 size() 调用,而对于真正的数组,它是微不足道的。
          • @Sergey Tachenov:我只在 Java 问题上获得了 5K+ 的代表,我知道 Java 数组是什么,谢谢;)我的意思是,如果 Sun 的某个神决定将那个“东西”命名为ArrayList 好吧...这意味着,不知何故,它必须与“数组”相关(现在你可以继续争论:但要么它是一个数组,我是对的太阳神在哪里当他们决定将这个集合命名为 ArrayList 时,这是错误和愚蠢的……我想你不会喜欢它;)
          猜你喜欢
          • 2021-05-12
          • 2021-01-12
          • 2011-07-19
          • 2019-01-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-18
          • 2016-08-02
          相关资源
          最近更新 更多