【问题标题】:Performance: Iterating through a List in Java性能:在 Java 中遍历列表
【发布时间】:2011-02-08 04:17:08
【问题描述】:

像这样在 Java 中遍历列表是否会更慢:

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

相对于:

for (Object o: list) {
    ... o
}

【问题讨论】:

    标签: java


    【解决方案1】:

    众所周知,随机和顺序的区别 * 访问经常是模糊的。例如,一些 List 实现 * 如果它们变得巨大但恒定,则提供渐近线性访问时间 * 在实践中的访问时间。这样的 List 实现 * 一般应该实现这个接口。根据经验,一个 * 列表实现应该实现这个接口,如果, * 对于类的典型实例,此循环: *

    * for (int i=0, n=list.size(); i * 运行速度比这个循环快: * * for (迭代器 i=list.iterator(); i.hasNext(); ) * i.next(); * *

    【讨论】:

      【解决方案2】:

      为该问题创建了一个microbenchmark,并惊讶地发现 for-each 的运行速度比索引循环快 2 到 3 倍。我唯一的解释是 for-each 版本可能不需要由 ArrayList.get(int index) 进行的范围检查。

      对于非常小的列表(10 个元素),结果大致相同。对于 100 个元素,for-each 速度提高 1.5 倍,对于 10000-100000 个元素,速度提高 2x-3 倍。

      测试是在随机数据集上运行的,最后校验和正在被验证,因此 JIT 校验不太可能发生在这些数据集上。

      【讨论】:

      • 更多比较在这里:dzone.com/articles/… 他们还使用“标准化”JMH 库进行微基准测试。
      【解决方案3】:

      我假设你是出于好奇而问的 并且不会引用 Knuth(某人 可能会)。

      我相信,一旦您的代码被编译,它就不会产生任何影响。它确实之前有所不同(示例 2 更具可读性和简洁性),因此请选择第 2 条,其余的不要关心。

      只要我的 2 美分

      编辑

      请注意,每次循环运行时,sn-p 编号 1 中的代码都会计算 list.size(),这可能会比编号 2 更慢

      又一次编辑

      我必须仔细检查一下,Joshua Bloch 建议使用 for each 循环(参见 Effective Java 的第 46 项)。我相信这会结束各种讨论。谢谢乔希! :)

      【讨论】:

      • +1 好点,即使第一个是在 LinkedList 上完成的,编译也可能提供相同的性能。
      • @Skeptic,即使现在没有,它肯定会在未来的版本中表现相同(或更好)
      • 过早优化不是万恶之源,复制/粘贴!!
      • 复制粘贴过早优化的代码怎么样?那是多么邪恶!
      • 通过放松疗法,您可以防止过早优化。
      【解决方案4】:

      我没有仔细研究所有 List 实现的 get() 的代码,所以也许我会写一些 FUD。我了解到的是,在 for 循环中使用 get(i) 将导致每次循环运行时一遍又一遍地对整个列表进行迭代。使用迭代器(就像增强的 for 循环一样)只会将迭代器移动到下一个列表元素,而不会再次迭代整个列表。

      我的结论是使用迭代器或增强的 for 循环应该更高效,因为使用 get() 会导致复杂性 O(n^2) 而不是 O(n)

      【讨论】:

      • 如上一个答案中所写,这取决于列表的类型。您所写的内容仅适用于未实现 RandomAccess 的列表(例如,对于 LinkedList,但不适用于 ArrayList)。
      【解决方案5】:

      可能会有所不同。

      如果 List 实现也实现了 java.util.RandomAccess(就像 ArrayList 一样),那么在交互器上使用它的 get() 方法会更快。

      如果它没有实现 java.util.RandomAccess(例如,LinkedList 没有),那么使用迭代器会快得多。

      但是,只有当您使用包含数千个(可能是分散的)对象或不断遍历的列表(就像对表示数字向量的列表对象执行大量数学运算一样)时,这才有意义。

      【讨论】:

      • 实现 RandomAccess 并不能保证 get() 很快。你可以写一个实现 RandomAccess 的链表——它会很慢。
      • @Kibiku - 是的,您可以编写 RandomAccess 违反其接口合同协议的实现。对于任何记录在案的接口合同,在编译时没有什么可以阻止这样做。那将是一个糟糕的编写或恶意编写的实现,这超出了 RandomAccess 应该提供的范围。在某些时候,作为一名工程师,您必须在沙地上画一条线,按照合同设计进行编程,并做出有效的推论。如果他/她违反了这样一个简单的界面标记的合同,我会质疑某人的编码能力。
      • 另外,就此而言,我可以创建一个实现 java.util.Map 的类,该类以只读模式运行,但它默默地忽略对 put()、remove() 和 putAll( ) 而不是抛出 UnsupportedOperationException() (这是由只读 Map 实例实现的预期行为。)除非您需要这样做的极端情况,否则能够这样做是不理解隐含合同的标志一个界面。接口不仅仅是一个标记或方法的集合。它们是开发人员应该遵循的语义契约。
      【解决方案6】:

      没有。当列表也实现时会更快(或者应该更快):RandomAccess(就像 ArrayList 和 LinkedList 一样)。

      但是,您应该始终使用后者:

       for( Object o: list ) {
       }
      

      只有在您有大量证据表明您在使用它时遇到性能问题时才切换到前者(例如,您分析了您的应用程序,因此您认为这是改进这部分代码的一个点)。

      如果不这样做,如果您的应用程序需要,您可能无法在以后的重构中切换您的实现(因为您将被绑定到 for( int i = 0 ; i &lt; list.size(); i++ ) 成语)。

      【讨论】:

        【解决方案7】:

        根据While loop, For loop and Iterator Performance Test – Java 中的基准测试和JavaRanch 问题“is using ArrayList.iterator() is faster than looping ArrayList in for loop?”,迭代器实际上有点慢。

        我仍然倾向于可读性,除非我对整个应用程序进行了基准测试并发现该循环是我的瓶颈。

        【讨论】:

        • 我认为 for-each 循环不会成为任何应用程序的瓶颈。
        • @Pablo:很难想象。循环的 contents 很容易成为瓶颈,但我怀疑 idiom 会有所作为。
        【解决方案8】:

        每次迭代检查大小确实会增加i 操作,但对性能影响不大。

        我的意思是两者之间存在细微差别

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

        对比:

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

        但这本质上并不重要。出于可读性考虑,请使用您问题中的第二个。

        【讨论】:

        • 我想知道为什么这里显示的做法如此普遍,而不是“for(int i=0,siz=myList.size(); i
        • 可能是因为这是一个不成熟的微优化,大多数人认为它不会提高可读性。无论如何,编译器都应该优化掉大小检查,即使在大多数情况下它不会成为瓶颈。
        【解决方案9】:

        普通列表的性能应该没有任何明显差异。

        对于链表,迭代器会快很多,尤其是对于大列表。

        【讨论】:

        • 什么是“普通列表”?我假设您的意思是“为ArrayList”。
        猜你喜欢
        • 2016-10-09
        • 2011-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-22
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多