以下两个循环之间是否有性能差异?
for (Object o: objectArrayList) {
o.DoSomething();
}
和
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
以下两个循环之间是否有性能差异?
for (Object o: objectArrayList) {
o.DoSomething();
}
和
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
来自 Joshua Bloch 的 Effective Java 中的第46条:
The for-each loop, introduced in release 1.5, gets rid of the clutter and the opportunity for error by hiding the iterator or index variable completely. The resulting idiom applies equally to collections and arrays:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
When you see the colon (:), read it as “in.” Thus, the loop above reads as “for each element e in elements.” Note that there is no performance penalty for using the for-each loop, even for arrays. In fact, it may offer a slight performance advantage over an ordinary for loop in some circumstances, as it computes the limit of the array index only once. While you can do this by hand (Item 45), programmers don’t always do so.
这些循环的作用完全相同,我只是想在发表个人意见之前先展示这些。
首先,经典的通过List循环的方式:
for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }
其次,这是首选的方式,因为它更少出错(你有多少次在循环嵌套中混淆了变量i和j而犯错了吗?)。
for (String s : strings) { /* do something using s */ }
第三,针对循环进行微优化:
int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }
get(int)
,另一个使用Iterator
。考虑到LinkedList
,因为它执行了n次get(int)
,所以for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
的性能要差得多。 - Steve Kuo性能影响大多数情况下不明显,但并非为零。如果您查看RandomAccess
接口的JavaDoc:
作为经验法则,如果List实现在类的典型实例中,以下循环:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
比这个循环运行速度更快:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
使用 for-each 循环的版本是使用迭代器,因此对于
ArrayList
等类型来说,for-each 循环不是最快的。
很不幸,这里存在一个差异。
如果你查看两种循环的生成字节码,它们是不同的。
以下是Log4j源代码的示例。
在 /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java 中,我们有一个名为 Log4jMarker 的静态内部类,其中定义了:
/*
* Called from add while synchronized.
*/
private static boolean contains(final Marker parent, final Marker... localParents) {
//noinspection ForLoopReplaceableByForEach
for (final Marker marker : localParents) {
if (marker == parent) {
return true;
}
}
return false;
}
使用标准循环:
private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
Code:
0: iconst_0
1: istore_2
2: aload_1
3: arraylength
4: istore_3
5: iload_2
6: iload_3
7: if_icmpge 29
10: aload_1
11: iload_2
12: aaload
13: astore 4
15: aload 4
17: aload_0
18: if_acmpne 23
21: iconst_1
22: ireturn
23: iinc 2, 1
26: goto 5
29: iconst_0
30: ireturn
使用for-each:
private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
Code:
0: aload_1
1: astore_2
2: aload_2
3: arraylength
4: istore_3
5: iconst_0
6: istore 4
8: iload 4
10: iload_3
11: if_icmpge 34
14: aload_2
15: iload 4
17: aaload
18: astore 5
20: aload 5
22: aload_0
23: if_acmpne 28
26: iconst_1
27: ireturn
28: iinc 4, 1
31: goto 8
34: iconst_0
35: ireturn
Oracle是怎么了?
我在Windows 7上尝试了Java 7和8。
使用foreach循环可以更清晰地表达代码的意图,通常比微小的速度提升更受欢迎。
每当我看到一个索引循环时,我都需要花费更长的时间来解析它是否按照我想象中的那样进行。例如,它从零开始吗?它包括还是排除终点等等?
我的大部分时间似乎都花在阅读代码上(无论是我自己编写的还是别人编写的),清晰度几乎总是比性能更重要。这些天容易忽略性能,因为Hotspot做得非常出色。
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
interface Function<T> {
long perform(T parameter, long x);
}
class MyArray<T> {
T[] array;
long x;
public MyArray(int size, Class<T> type, long x) {
array = (T[]) Array.newInstance(type, size);
this.x = x;
}
public void forEach(Function<T> function) {
for (T element : array) {
x = function.perform(element, x);
}
}
}
class Compute {
int factor;
final long constant;
public Compute(int factor, long constant) {
this.factor = factor;
this.constant = constant;
}
public long compute(long parameter, long x) {
return x * factor + parameter + constant;
}
}
public class Main {
public static void main(String[] args) {
List<Long> numbers = new ArrayList<Long>(50000000);
for (int i = 0; i < 50000000; i++) {
numbers.add(i * i + 5L);
}
long x = 234553523525L;
long time = System.currentTimeMillis();
for (int i = 0; i < numbers.size(); i++) {
x += x * 7 + numbers.get(i) + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
time = System.currentTimeMillis();
for (long i : numbers) {
x += x * 7 + i + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
numbers = null;
MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return x * 8 + parameter + 5L;
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
myArray = null;
myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return new Compute(8, 5).compute(parameter, x);
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
}
}
224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895
我认为其他答案的基础是错误的基准测试,这并没有考虑Hotspot的编译和优化过程。
如果可能的话,请使用增强型循环,因为大多数情况下它是最快的。如果无法使用增强型循环,则尽可能将整个数组提取到本地变量中:
int localArray = this.array;
for (int i = 0; i < localArray.length; i++) {
methodCall(localArray[i]);
}
通常情况下没有区别,因为Hotspot在优化和消除Java需要执行的检查方面非常出色。
但是有时候某些优化只是无法完成,通常是因为您在循环内部有一个虚拟调用,这不能被内联。
在这种情况下,一些循环确实比其他循环更快。
Java 需要做的事情之一:
考虑以下 C 风格循环:
for (int i = 0; i < this.array.length; i++) { //now java knows i < this.array.length
methodCall(this.array[i]);// no need to check
}
int len = this.array.length;//len is now a local variable
for (int i = 0; i < len; i++) { //no reload needed
methodCall(this.array[i]); //now java will do the check
}
现在Java不需要每次重新加载,因为局部变量可能无法被方法调用和/或另一个线程改变。局部变量只能在方法内部更改,Java现在可以证明变量len不会改变。
但现在循环条件i < this.array.length已更改为i < len,以前的优化失败了,Java需要检查i是否在this.array的边界内。
更好的优化是将整个数组提取到局部变量中:
ArrayType[] localArray = this.array;
for (int i = 0; i < localArray.length; i++) {
methodCall(localArray[i]);
}
ArrayList localArray = this.array
,你只是复制了一个引用,没有进行额外的内存分配,除了在栈上创建一个对象引用之外,基本上没有开销。 - Jerry Lundegaard