在for循环比较中使用集合大小

时间:2010-12-14 11:36:46

标签: java collections

Java中的Collections的size()方法是否有编译器优化?

请考虑以下代码:

for(int i=0;i<list.size();i++)
      ...some operation.....

每个i都调用了size()方法。找出尺寸并重复使用它不是更好吗? (方法调用有开销)。

final int len = list.size()
for(int i=0;i<len;i++)
      ...some operation.....

然而,当我为这两个代码片段计时时,没有显着的时间差异,即使我高达10000000。 我在这里错过了什么吗?

Update1:​​我知道除非集合发生变化,否则不再计算大小。但是必须有一些与方法调用相关的开销。编译器是否总是内联这些(参见Esko的答案)?

更新2:我的好奇心得到了进一步的推动。从给出的答案中,我看到好的JIT编译器经常会内联这个函数调用。但他们仍然需要确定该集合是否被修改。我不接受答案,希望有人能指出编译器如何处理这个问题。

4 个答案:

答案 0 :(得分:13)

好的,这是JDK源代码的摘录(JDK文件夹中的src.zip):

public int size() {
    return size;
}

这是来自ArrayList,但我认为其他集合有类似的实现。现在,如果我们想象编译器内联size()调用(这将是完全合理的),你的循环将转变为:

for(int i=0;i<list.size;i++)
// ...

(好吧,让我们忘记大小是私有的。)编译器如何检查集合是否被修改?答案是它没有也不需要这样做,因为该字段已经在字段中可用,所以它所要做的就是在每次迭代时访问size字段,但访问int变量非常快操作。请注意,它可能只计算一次地址,因此在每次迭代时甚至不必取消引用列表。

当通过add()方法修改集合时会发生什么?

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

如您所见,它只是增加了大小字段。因此编译器实际上不需要做任何事情来确保它可以访问最新的大小。唯一的例外是,如果您从另一个线程修改集合需要同步,否则循环线程可能会看到其本地缓存的大小值,可能会更新也可能不会更新。

答案 1 :(得分:9)

集合的.size()方法返回的值通常仅在修改实际集合时缓存并重新计算(添加新元素或删除旧元素)。

不要比较for循环控制范围,而是尝试使用for each循环,因为它实际上使用Iterator,这在某些集合实现中比使用索引迭代要快得多。

答案 2 :(得分:0)

调用集合的size()方法只返回一个已经跟踪的整数值。没有太大的时间差异,因为size()实际上并不计算项目的数量,而是在添加或删除项目时跟踪项目的数量。

答案 3 :(得分:0)

java language specification解释说,表达式是在每个迭代步骤上计算的。以您为例,list.size()被称为10.000.000次。

这在您的情况下无关紧要,因为列表实现(通常)具有存储实际列表大小的私有属性。但如果评估真的需要时间,它可能会带来麻烦。在这些情况下,建议将表达式的结果存储到局部变量中。