在我们的应用程序中将 for 循环更新为 for-each 循环时,我遇到了很多这样的“模式”:
for (int i = 0, n = a.length; i < n; i++) {
...
}
代替
for (int i = 0; i < a.length; i++) {
...
}
我可以看到您获得了集合的性能,因为您不需要在每个循环中调用size()方法。但是用数组??
那么问题来了:array.length
比常规变量更昂贵吗?
在我们的应用程序中将 for 循环更新为 for-each 循环时,我遇到了很多这样的“模式”:
for (int i = 0, n = a.length; i < n; i++) {
...
}
代替
for (int i = 0; i < a.length; i++) {
...
}
我可以看到您获得了集合的性能,因为您不需要在每个循环中调用size()方法。但是用数组??
那么问题来了:array.length
比常规变量更昂贵吗?
不,调用array.length
isO(1)
或恒定时间操作。
由于.length
is(行为类似于) 的public
final
成员array
,因此访问它并不比访问局部变量慢。(这与调用类似的方法非常不同size()
)
.length
无论如何, 现代 JIT 编译器都可能会优化对 right out 的调用。
您可以通过查看 OpenJDK 中 JIT 编译器的源代码或让 JVM 转储 JIT 编译的本机代码并检查代码来确认这一点。
请注意,JIT 编译器可能无法执行此操作;例如
我在午餐时有一点时间:
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
笔记:
n = a.length
显示速度比i < a.length
大约一半快,可能是由于垃圾收集(?)。250000000
大得多,因为我得到OutOfMemoryError
了270000000
。关键是,它是其他人一直在做的,你必须在内存不足的情况下运行 Java,你仍然看不到这两种替代方案在速度上的显着差异。把你的开发时间花在真正重要的事情上。
我怀疑是否有任何显着差异,即使有,我敢打赌它可能在编译过程中被优化掉了。当您尝试对此类事情进行微优化时,您是在浪费时间。首先使代码可读且正确,然后如果您有性能问题,请使用分析器,然后担心选择更好的数据结构/算法(如果合适),然后担心优化分析器突出显示的部分。
数组的长度在 Java 中存储为数组的成员变量(与元素不同),因此获取该长度是一个常数时间操作,与从常规类中读取成员变量相同。许多旧语言(如 C 和 C++)不将长度存储为数组的一部分,因此您希望在循环开始之前存储长度。您不必在 Java 中这样做。
在那种情况下,你为什么不做一个逆循环:
for (int i = a.length - 1; i >= 0; --i) {
...
}
这里有2个微优化:
将它存储在变量中可能会稍微快一些。但是,如果分析器指出这是一个问题,我会感到非常惊讶。
在字节码级别,获取数组的长度是通过arraylength
字节码完成的。我不知道它是否比iload
字节码慢,但应该没有足够的差异来引起注意。
这个答案是从 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 个都生成相同的本机代码。
然而,第一种形式也可以说更容易被人们阅读,所以我会说去吧。
Array.length 是一个常量,JIT 编译器在这两种情况下都应该看穿它。我希望得到的机器代码在这两种情况下都是相同的。至少对于服务器编译器。