【问题标题】:Equivalence of for loop to List.get in javajava中for循环与List.get等价
【发布时间】:2012-06-14 20:44:56
【问题描述】:

我有一个包含一个元素的列表,但以后可能会有更多。我使用的列表是一个 ArrayList。我发现通过调用 List.get(0) 而不是使用 for 循环可以获得更快的结果。为什么是这样?循环 1 项不应该与获取 1 项相同吗?如果没有,那么我怎样才能获得类似的性能?我知道我的数组最终会大于一项。我在 opengl 渲染器的主体中使用这个循环。当我使用循环时,我的 fps 会下降 45。

编辑:我已经解决了这个问题。每次渲染时,我的渲染器都会在列表中添加一个新值。

【问题讨论】:

  • “更快的结果”?这可能是一个无关紧要的微优化。与测试工具之外的操作相比,它会相形见绌。我的建议?在实际应用中测量出有意义的差异之前,请不要担心。
  • 演示您描述的行为的代码示例将非常有帮助。列表有很多种,for 循环的编写方法也很多。
  • 一个 ArrayList 在直接访问这样的元素方面会非常快。在数组列表上循环至少需要最少的启动(至少,设置索引,启动循环等,如果你使用了迭代器,对象创建等等。)
  • arraylist 是我使用 opengl 渲染的网格列表。我需要这样的快速性能。
  • 我的目标是使用您的循环,以便您的代码易于更改,而不必担心性能。你测量过差异吗?这实际上是一个问题,还是您只是担心未来的表现?

标签: java list foreach


【解决方案1】:

在 Java 中使用增强的 for 循环(for-each)会产生如下编译代码:

Iterator<Thing> it = list.iterator();
while(it.hasNext()) {
  yourLoop.loopBody(it.next());
}

您可能期望它等同于这个?

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

但事实并非如此,构建迭代器需要您观察到的额外时间。

【讨论】:

  • 即使 for 循环也有额外的开销:它调用了一个额外的方法并创建了一个新变量。
  • 那个for循环比较快,但是跳过删除for循环还是比较快的。我需要这个 for 循环才能让我的代码稍后运行。
  • 您可能会从首先手动检查尺寸获得小幅提升。即时编译器将对 size 的调用内联为直接字段访问。例如,int size = list.size(); if (size == 1) {list.get(0);} else {for (int i = 0; i &lt; size; i++) {}}
【解决方案2】:

在不知道List 实现的情况下,无法真正给出具体答案。例如,ArrayList 是由一个数组支持的,所以调用get 本质上是一个数组访问。

另一方面,使用for 循环的“foreach”版本需要创建Iterator。这可能是减速的原因。一些实现对IteratorListIterator 有复杂的实现。

那么答案就是风格和可读性的问题。大多数 Java 程序员会说 foreach 循环更具可读性并且更清楚您的意图。并且使用List(例如LinkedList)的一些实现要快得多。

【讨论】:

  • 迭代 LinkedList 并不应该比迭代数组快。对于 ArrayList 数组访问、位置增量和边界检查是恒定时间。同样对于 LinkedList,节点值访问、节点下一个和边界检查也是恒定时间。当您在列表中的随机点(并不总是在末尾)进行插入时,LinkedList 的执行速度通常比 ArrayList 快,因为 LinkedList 具有恒定时间插入,而 arraylist 则没有。
  • @Matt,我的意思是,如果有人做类似for (int i=0;i&lt;list.size();i++) {Object ele = list.get(i);} 的事情,ArrayList 会很快,LinkedList 会很慢。原因很简单,一个使用数组访问,另一个必须遍历每个元素,直到它到达索引。当然,在 LinkedList 的特定情况下,如果所需的索引位于列表的后半部分,实现者足够聪明,可以从尾部向后迭代。
【解决方案3】:

我不是收藏方面的专家,但设置迭代肯定比简单地获取特定项目更昂贵。

通过设置foreach,我们不知道集合中有多少项目,因此无论列表有多少项目,它都会设置它进行迭代。

【讨论】:

    【解决方案4】:

    总的来说,如果您担心速度,如果您知道自己正在使用 ArrayList,那么您最快的方法是:

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

    虽然“size”方法的开销很小,但它仍然是不需要在每个循环中调用的开销。 ArrayList 上的 get() 方法是 O(1)。这应该给迭代器类似的性能。如果您查看 ArrayList 的迭代器代码,它非常简单,唯一真正的额外开销来自对象创建和并发修改检查。

    然而,对于 LinkedList,这种类型的循环会产生可怕的性能,因为 LinkedList 上的 .get() 方法是 O(n),而 Iterator 会快得多,因为它只是对每个循环进行指针检查和重新分配迭代使得每次调用 next() 的时间为 O(1)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-23
      • 2020-07-10
      • 1970-01-01
      • 2010-11-13
      • 2022-01-12
      • 2015-03-21
      • 1970-01-01
      相关资源
      最近更新 更多