for循环和for-each循环在性能上有区别吗?

如果有的话,下面两个循环之间的性能差异是什么?

for (Object o: objectArrayList) {
o.DoSomething();
}

而且

for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
135311 次浏览

出自Joshua Bloch 有效的Java第46项:

for-each循环,在 1.5版摆脱了混乱 犯错的机会 隐藏迭代器或索引变量 完全。产生的习语 同样适用于集合和 数组:< / p >
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
doSomething(e);
}

当您看到冒号(:)时,将其读为 “在。”因此,上面的循环读作 "对于元素中的每个元素e。"请注意 没有性能损失 使用For -each循环,甚至For 数组。事实上,这可能是一种怠慢 性能优于普通 For循环在某些情况下,如它 计算数组索引的限制 只有一次。而你可以通过 hand(第45项),程序员不需要

即使使用像ArrayList或Vector这样的东西,其中“get”是一个简单的数组查找,第二个循环仍然有第一个循环没有的额外开销。我预计它会比第一次慢一点。

唯一确定的方法是对它进行基准测试,即使是事情没有听起来那么简单。JIT编译器可以对代码做一些意想不到的事情。

for-each循环通常是首选的。如果您使用的List实现不支持随机访问,那么“get”方法可能会慢一些。例如,如果使用LinkedList,则会产生遍历代价,而For -each方法使用迭代器跟踪其在列表中的位置。关于for-each循环的细微差别的更多信息。

我想文章现在在这里:新位置

这里显示的链接已经失效。

所有这些循环都是一样的,我只是想在发表我的观点之前展示一下。

首先,循环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 */ }

第三,微优化for循环:

int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }

现在真正的两美分:至少当我测试这些时,第三个是最快的,当计算每种类型的循环所花费的毫秒数时,其中一个简单的操作重复了数百万次——这是在Windows上使用Java 5和jre1.6u10,如果有人感兴趣的话。

虽然至少看起来第三个优化是最快的,但您真的应该问问自己,是否愿意冒险在循环代码的所有地方实现这种窥视孔优化,因为根据我所看到的,实际的循环通常不是任何实际程序中最耗时的部分(或者可能我只是在错误的领域工作,谁知道呢)。而且就像我在Java for - each循环的借口中提到的那样(一些人把它称为迭代器循环,另一些人把它称为工党循环),在使用它时,你不太可能碰到那个特定的愚蠢错误。在讨论这个字节码如何比其他字节码更快之前,请记住javac根本不优化字节码(好吧,至少几乎不优化),它只是编译字节码。

如果你喜欢微观优化,或者你的软件使用了很多递归循环,那么你可能会对第三种循环感兴趣。只需要记住,在更改for循环之前和之后,都要对软件进行良好的基准测试。

使用迭代器总是比使用索引更好。这是因为iterator最有可能针对List实现进行了优化,而索引(调用get)可能没有。例如,LinkedList是一个List,但是通过它的元素建立索引将比使用迭代器迭代慢。

Foreach使你的代码的意图更清晰,这通常比非常小的速度改进更受欢迎——如果有的话。

每当我看到一个索引循环,我必须解析它稍长一点,以确保它做什么我认为它做,例如,它是否从0开始,它是否包括或排除终点等?

我的大部分时间似乎都花在了阅读代码(我写的或别人写的)上,而清晰度几乎总是比性能更重要。现在很容易忽略Hotspot的性能,因为Hotspot做得非常出色。

对性能的影响基本上是微不足道的,但也不是零。如果你看RandomAccess接口的JavaDoc:

作为一个经验法则,一个列表 实现应该实现这个 的典型实例 类,这个循环:

for (int i=0, n=list.size(); i < n; i++)
list.get(i);

运行速度比这个循环快:

for (Iterator i=list.iterator(); i.hasNext();)
i.next();

而for-each循环使用version with iterator,因此以ArrayList为例,for-each循环不是最快的。

以下代码:

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

我运行的是带有OracleJDK 1.7更新6的Ubuntu 12.10 alpha。

一般来说,HotSpot优化了大量的间接操作和简单的冗余操作,所以一般情况下,您不必担心它们,除非它们有很多顺序或嵌套严重。

另一方面,LinkedList上的索引get比LinkedList上的next On迭代器要慢得多,所以当你使用迭代器(显式或隐式地在for-each循环中)时,你可以避免性能损失,同时保持可读性。

通过变量名objectArrayList,我假设它是java.util.ArrayList的一个实例。在这种情况下,性能差异是不明显的。

另一方面,如果它是java.util.LinkedList的实例,第二种方法将会慢得多,因为List#get(int)是一个O(n)操作。

因此,第一种方法总是首选的,除非循环中的逻辑需要索引。

不幸的是,两者之间似乎存在差异。

如果查看这两种循环生成的字节代码,就会发现它们是不同的。

下面是来自Log4j源代码的一个示例。

在/log4j-api/src/main/java/org/apache/ loggging/ 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

那个神谕是怎么回事?

我在Windows 7上用Java 7和Java 8尝试过。

1. for(Object o: objectArrayList){
o.DoSomthing();
}
and


2. for(int i=0; i<objectArrayList.size(); i++){
objectArrayList.get(i).DoSomthing();
}

两者都做同样的事情,但为了方便和安全的编程使用,在第二种使用方式中有容易出错的可能性。

以下是Android开发团队对两者差异的简要分析:

https://www.youtube.com/watch?v=MZOf3pOAM6A

结果是存在的差异,在非常有限的环境中,有非常大的列表,这可能是一个显著的差异。在他们的测试中,for每个循环花费了两倍的时间。然而,他们的测试是针对400,000个整数的数组列表进行的。数组中每个元素的实际差值是6 微秒。我还没有测试,他们也没有说,但我预计使用对象而不是原语的区别会稍微大一些,但即使如此,除非你正在构建库代码,而你不知道你将被要求迭代的范围,我认为这种区别不值得强调。

奇怪的是,没有人提到显而易见的——foreach分配内存(以迭代器的形式),而普通的for循环不分配任何内存。对于Android游戏来说,这是个问题,因为这意味着垃圾收集器将周期性地运行。在游戏中,你不希望垃圾回收器运行……永远。所以不要在你的绘制(或渲染)方法中使用foreach循环。

Accepted answer回答了这个问题,除了ArrayList…

因为大多数开发人员都依赖于ArrayList(至少我是这么认为的)

所以我有义务在这里加上正确答案。

直接从开发人员文档:-

增强的for循环(有时也称为“for-each”循环)可用于实现Iterable接口的集合和数组。对于集合,会分配一个迭代器来对hasNext()和next()进行接口调用。使用ArrayList,手工编写的计数循环大约快3倍(有或没有JIT),但对于其他集合,增强的for循环语法将完全等同于显式迭代器的使用。

有几种迭代数组的方法:

static class Foo {
int mSplat;
}


Foo[] mArray = ...


public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}


public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;


for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}


public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}

zero()是最慢的,因为JIT还不能优化掉每次循环迭代获取数组长度的成本。

一个()比较快。它将所有内容提取到局部变量中,避免了查找。只有数组长度能带来性能上的好处。

two()对于没有JIT的设备来说是最快的,对于有JIT的设备来说与one()是没有区别的。它使用Java编程语言1.5版中引入的增强for循环语法。

因此,默认情况下您应该使用增强的for循环,但是可以考虑使用手写的计数循环来进行性能关键的ArrayList迭代。

在我看来,其他答案都是基于错误的基准测试,没有考虑Hotspot的编译和优化过程。

简短的回答

尽可能使用增强循环,因为大多数时候它是最快的。 如果不能,如果可能的话,将整个数组拉到一个局部变量中:

int localArray = this.array;
for (int i = 0; i < localArray.length; i++) {
methodCall(localArray[i]);
}

长回答

现在,通常没有区别,因为Hotspot非常擅长优化和消除java需要做的检查。

但有时一些优化就是不能做,通常是因为你在循环中有一个虚拟调用,不能内联。 在这种情况下,一些循环确实比其他循环快

Java需要做的一些事情:

  • < >强重载this.array < / >强 -因为它可以被改变(通过调用或另一个线程)
  • 检查i是否在数组的边界内,如果不是把IndexOutOfBoundsException
  • 检查被访问对象引用是否为空,如果是抛出NullPointerException

考虑一下这个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
}
通过计算循环条件< >强我& lt;this.array.length < / >强, java知道我<强> < / >强 必须在边界内(我<强> < / >强只改变了调用),所以不需要在下一行中再次检查它。 但在这种情况下,java需要重新加载< >强this.array.length < / >强.

你可能会想要“优化”;循环,通过将< >强this.array.length < / >强值拉到局部变量内部:

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不需要每次都重新加载,因为局部变量可以被methodCall和/或另一个线程改变。局部变量只能在方法本身内部更改,java现在可以证明变量< >强len < / >强不能更改。

但现在循环条件< >强我& lt;this.array.length < / >强变为< >强我& lt;len < / >强,之前的优化失败,java需要检查我<强> < / >强是否在< >强this.array < / >强的边界内。

一个更好的优化方法是将整个数组拉入一个局部变量:

ArrayType[] localArray = this.array;
for (int i = 0; i < localArray.length; i++) {
methodCall(localArray[i]);
}

现在java不需要重新加载数组,并且“我<强> < / >强 in bounds"支票也被取消了。

那么增强循环呢? 好吧,通常编译器会把你的增强循环重写成类似上次显示的循环的东西,如果不是更好的话