【问题标题】:for-each vs for vs whilefor-each vs for vs while
【发布时间】:2011-05-20 09:17:07
【问题描述】:

我想知道在 ArrayList 或各种 List 上实现“for-each”循环的最佳方法是什么。

以下哪个实现是最好的,为什么?或者有没有最好的办法?

感谢您的帮助。


List values = new ArrayList();

values.add("one"); values.add("two"); values.add("three"); ...

//#0
for(String value : values) { ... }

//#1
for(int i = 0; i < values.size(); i++) { String value = values.get(i); ... }

//#2
for(Iterator it = values.iterator(); it.hasNext(); ) { String value = it.next(); ... }

//#3
Iterator it = values.iterator(); while (it.hasNext()) { String value = (String) it.next(); ... }

【问题讨论】:

标签: java for-loop while-loop


【解决方案1】:

简短的回答是使用版本 0。看看Android's documentation for Designing for Performance 的部分标题使用增强的 For 循环语法。该页面有很多好东西,非常清晰简洁。

【讨论】:

  • 那是针对android的,但是在这个问题中没有Android TAG,这也取决于你最后需要什么。
【解决方案2】:

#3 有一个缺点,因为迭代器it 的范围超出了循环的末尾。其他解决方案没有这个问题。

#2 和#0 完全一样,只是#0 更易读,更不容易出错。

#1(可能)效率较低,因为它每次通过循环都会调用.size()

#0 通常是最好的,因为:

  • 它是最短的
  • 它最不容易出错
  • 它是惯用的,其他人一眼就能看懂
  • 由编译器高效实现
  • 它不会用不必要的名称污染您的方法范围(循环外)

【讨论】:

  • 不错的答案。但是,更新时需要 #1(如果不只是改变当前项目或将结果构建为新列表)并附带索引。由于在这种情况下 List&lt;&gt;ArrayList&lt;&gt;,因此 get()(和 size())是 O(1),但对于所有 List-contract 类型来说并不相同。
  • #2 是在循环期间需要删除东西时使用的解决方案。
【解决方案3】:

在我看来,#0 是最容易阅读的,但 #2 和 #3 也可以。这三者之间应该没有性能差异。

几乎在任何情况下都不应使用 #1。您在问题中声明您可能想要遍历“每种列表”。如果您碰巧在 LinkedList 上进行迭代,那么 #1 将是 n^2 复杂性:不好。即使您绝对确定您正在使用支持有效随机访问的列表(例如ArrayList),通常也没有理由将 #1 用于其他任何列表。

【讨论】:

  • 不应该永远使用#1。这是更改通过的列表的单元格的唯一方法,尽管必须考虑时间复杂度(List 没有隐含)。
  • 更改单元格?什么意思?
  • for (int i = 0; i &lt; x.size(); i++) { x.set(i, i * 2); } 当然,这是一个愚蠢的例子,在许多情况下,创建一个新列表会更好。此外,在这种情况下,限制为 ArrayList 可能更合适。但它从不的反击。
  • 对。很公平。虽然我仍然建议使用 ListIterator 并且优先使用 set 方法。然后它仍然可以有效地处理非随机访问列表。
  • 我已经软化了“从不”一点:)
【解决方案4】:

回应 OP 的此评论。

但是,更新时需要 #1(如果不只是改变当前项目或将结果构建为新列表)并附带索引。由于在这种情况下 List 是一个 ArrayList,因此 get()(和 size())是 O(1),但对于所有 List-contract 类型来说并不相同。

让我们看看这些问题:

对于List 合约的所有实现,get(int) 肯定不是O(1)。但是,对于java.util 中的所有List 实现,AFAIK,size()O(1)。但是您是正确的,对于许多 List 实现来说,#1 不是最理想的。事实上,对于像LinkedList 这样的列表,其中get(int)O(N),#1 方法会导致O(N^2) 列表迭代。

ArrayList 的情况下,手动提升size() 的调用,将其分配给(最终)局部变量是一件简单的事情。通过这种优化,#1 代码明显快于其他情况……ArrayLists。

您关于在迭代元素时更改列表的观点引发了许多问题:

  • 如果您使用显式或隐式使用迭代器的解决方案执行此操作,则根据列表类,您可能会得到ConcurrentModificationExceptions。如果您使用其中一个并发集合类,则不会出现异常,但 javadocs 声明迭代器不一定会返回所有列表元素。

  • 如果您使用#1 代码(按原样)执行此操作,那么您就有问题了。如果修改是由同一个线程执行的,则需要调整索引变量以避免丢失条目,或者返回两次。即使一切正确,在当前位置之前同时插入的列表条目也不会显示。

  • 如果 #1 情况下的修改由不同的线程执行,则很难正确同步。核心问题是get(int)size()是独立的操作。即使它们是单独同步的,也没有什么可以阻止其他线程在 sizeget 调用之间修改列表。

简而言之,迭代一个同时修改的列表很棘手,应该避免...... 除非你真的知道你在做什么

【讨论】:

    猜你喜欢
    • 2023-03-17
    • 2014-10-19
    • 2011-10-31
    • 2013-01-26
    • 1970-01-01
    • 2011-11-05
    • 1970-01-01
    • 2020-05-03
    • 1970-01-01
    相关资源
    最近更新 更多