在我们的应用程序中将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
的调用是O(1)
或常量时间操作。
由于.length
就像array
的public final
成员一样,所以访问速度与访问本地变量无异。(这与调用类似size()
方法不同)
现代JIT编译器很可能会优化掉对.length
的调用。
您可以通过查看OpenJDK中JIT编译器的源代码或者让JVM转储编译后的本机代码并检查代码来确认这一点。
请注意,有些情况下JIT编译器可能无法进行优化,例如:
@jjnguy
:调用array.length
的时间复杂度不可以是O(1),而且它可能比int len = array.length; i < len;
还要更长,尽管后者的时间复杂度也是O(1)。 - Grant Wagner.length
的调用进行优化,以便更好地优化代码。[需要引证来源] - xehpuk我在午餐时间有点空闲:
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
设得更大,因为我在 270000000
时遇到了 OutOfMemoryError
。重点是,正如其他人所说,你必须让Java耗尽内存,但仍然看不到两种替代方案之间速度上的显着差异。把你的开发时间花在真正有意义的事情上。
我怀疑这样做根本没有任何显著的区别,即使有,也很可能在编译时被优化掉了。当你试图进行微观优化时,你只是浪费时间。首先要让代码可读且正确,如果有性能问题,使用分析工具,然后才考虑选择更好的数据结构/算法(如果适用),然后再担心优化分析工具显示的部分。
.length
要花费更长的时间。 - Grant Wagner在Java中,数组的长度被存储为数组的成员变量(而不是元素),因此获取该长度是一个常数时间操作,与从普通类读取成员变量相同。许多旧语言(如C和C ++)不将长度存储为数组的一部分,因此您需要在循环开始之前存储长度。在Java中不需要这样做。
把它存储在一个变量中可能会稍微快一点。但是如果分析器指出它是一个问题,我会非常惊讶。
在字节码级别上,获取数组的长度是通过arraylength
字节码完成的。我不知道它是否比iload
字节码慢,但是差异应该不足以注意到。
for (int i = a.length - 1; i >= 0; --i) {
...
}
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执行的话,我希望所有三个都会生成相同的本地代码。
然而,第一种形式也可以说更容易被人理解,所以我建议选择那个。
Array.length是一个常量,JIT编译器应该在两种情况下都能看到它。我希望生成的机器代码在两种情况下都是相同的。至少对于服务器编译器来说是这样。