58

在我们的应用程序中将 for 循环更新为 for-each 循环时,我遇到了很多这样的“模式”:

for (int i = 0, n = a.length; i < n; i++) {
    ...
}

代替

for (int i = 0; i < a.length; i++) {
    ...
}

我可以看到您获得了集合的性能,因为您不需要在每个循环中调用size()方法。但是用数组??

那么问题来了:array.length比常规变量更昂贵吗?

4

8 回答 8

78

不,调用array.lengthisO(1)或恒定时间操作。

由于.lengthis(行为类似于) 的public final成员array,因此访问它并不比访问局部变量慢。(这与调用类似的方法非常不同size()

.length无论如何, 现代 JIT 编译器都可能会优化对 right out 的调用。

您可以通过查看 OpenJDK 中 JIT 编译器的源代码或让 JVM 转储 JIT 编译的本机代码并检查代码来确认这一点。

请注意,JIT 编译器可能无法执行此操作;例如

  1. 如果您正在调试封闭方法,或者
  2. 如果循环体有足够的局部变量来强制寄存器溢出。
于 2009-07-30T18:12:01.147 回答
19

我在午餐时有一点时间:

public static void main(String[] args) {
    final int[] a = new int[250000000];
    long t;

    for (int j = 0; j < 10; j++) {
        t = System.currentTimeMillis();
        for (int i = 0, n = a.length; i < n; i++) { int x = a[i]; }
        System.out.println("n = a.length: " + (System.currentTimeMillis() - t));

        t = System.currentTimeMillis();
        for (int i = 0; i < a.length; i++) { int x = a[i]; }
        System.out.println("i < a.length: " + (System.currentTimeMillis() - t));
    }
}

结果:

n = a.length: 672
i < a.length: 516
n = a.length: 640
i < a.length: 516
n = a.length: 656
i < a.length: 516
n = a.length: 656
i < a.length: 516
n = a.length: 640
i < a.length: 532
n = a.length: 640
i < a.length: 531
n = a.length: 641
i < a.length: 516
n = a.length: 656
i < a.length: 531
n = a.length: 656
i < a.length: 516
n = a.length: 656
i < a.length: 516

笔记:

  1. 如果您反转测试,则n = a.length显示速度比i < a.length大约一半快,可能是由于垃圾收集(?)。
  2. 我不能做250000000大得多,因为我得到OutOfMemoryError270000000

关键是,它是其他人一直在做的,你必须在内存不足的情况下运行 Java,你仍然看不到这两种替代方案在速度上的显着差异。把你的开发时间花在真正重要的事情上。

于 2009-07-30T18:39:50.577 回答
11

我怀疑是否有任何显着差异,即使有,我敢打赌它可能在编译过程中被优化掉了。当您尝试对此类事情进行微优化时,您是在浪费时间。首先使代码可读且正确,然后如果您有性能问题,请使用分析器,然后担心选择更好的数据结构/算法(如果合适),然后担心优化分析器突出显示的部分。

于 2009-07-30T18:13:08.650 回答
7

数组的长度在 Java 中存储为数组的成员变量(与元素不同),因此获取该长度是一个常数时间操作,与从常规类中读取成员变量相同。许多旧语言(如 C 和 C++)不将长度存储为数组的一部分,因此您希望在循环开始之前存储长度。您不必在 Java 中这样做。

于 2009-07-30T18:19:43.223 回答
4

在那种情况下,你为什么不做一个逆循环:

for (int i = a.length - 1; i >= 0; --i) {
    ...
}

这里有2个微优化:

  • 循环反转
  • 后缀减量
于 2009-07-31T17:33:51.550 回答
3

将它存储在变量中可能会稍微快一些。但是,如果分析器指出这是一个问题,我会感到非常惊讶。

在字节码级别,获取数组的长度是通过arraylength字节码完成的。我不知道它是否比iload字节码慢,但应该没有足够的差异来引起注意。

于 2009-07-30T18:11:37.123 回答
2

这个答案是从 C# 的角度来看的,但我认为这同样适用于 Java。

在 C# 中,成语

for (int i = 0; i < a.length; i++) {    ...}

被识别为遍历数组,因此在循环中访问数组时避免边界检查,而不是每次访问数组时。

这可能会或可能不会被以下代码识别:

for (int i = 0, n = a.length; i < n; i++) {    ...}

或者

n = a.length;

for (int i = 0; i < n; i++) {   ...}

我不知道编译器与 JITter 执行了多少这种优化,特别是如果它是由 JITter 执行的,我希望所有 3 个都生成相同的本机代码。

然而,第一种形式也可以说更容易被人们阅读,所以我会说去吧。

于 2009-07-30T18:24:03.807 回答
1

Array.length 是一个常量,JIT 编译器在这两种情况下都应该看穿它。我希望得到的机器代码在这两种情况下都是相同的。至少对于服务器编译器。

于 2009-07-30T19:47:02.877 回答