for循环和for循环之间是否存在性能差异?

时间:2008-11-02 12:40:53

标签: java performance for-loop

以下两个循环之间的性能差异是什么?

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

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

17 个答案:

答案 0 :(得分:195)

约书亚布洛赫Effective Java中的第46项:

  

引入的for-each循环   释放1.5,摆脱杂乱   和错误的机会   隐藏迭代器或索引变量   完全。由此产生的成语   同样适用于收藏品和   阵列:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}
     

当您看到冒号(:)时,请将其读作   “in。”因此,上面的循环读作为   “对于元素中的每个元素e。”注意   没有性能损失   使用for-each循环,即使是   阵列。事实上,它可能会提供轻微的   性能优于普通   在某些情况下,循环,因为它   计算数组索引的限制   只有一次。虽然你可以做到这一点   手(项目45),程序员没有   总是这样做。

答案 1 :(得分:27)

所有这些循环完全相同,我只想在投入我的两分钱之前展示这些。

首先,循环浏览List的经典方法:

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

第二,首选的方式,因为它不容易出错(多少次你完成了“oops,在循环中的这些循环中混合变量i和j”的事情?)。

for(String s : strings) { /* do something using s */ }

第三,微优化for循环:

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

现在实际的两分钱:至少当我测试这些时,第三个是最快的,当计算毫秒数时,每种类型的循环需要花费多长时间,其中一个简单的操作重复几百万次 - 这是在Windows上使用带有jre1.6u10的Java 5,以防有人感兴趣。

虽然它至少似乎是第三个是最快的,但你真的应该问自己是否想要冒险在你的循环代码中到处实现这个窥孔优化,因为从我看到的,实际循环通常不是任何真实程序中最耗时的部分(或者我可能只是在错误的领域工作,谁知道)。而且就像我在Java for-each循环的借口中提到的那样(有些将其称为 Iterator循环,其他则称为 for-in循环 >)使用它时,你不太可能遇到那个特别愚蠢的错误。在讨论如何甚至比其他的更快之前,请记住javac根本不优化字节码(好吧,几乎无论如何),它只是编译它。

如果您正在进行微优化和/或您的软件使用大量递归循环等,那么您可能会对第三种循环类型感兴趣。只需记住在更改for循环之前和之后都要对您的软件进行基准测试,以获得这个奇怪的,微优化的。

答案 2 :(得分:12)

通常应首选for-each循环。如果您使用的List实现不支持随机访问,则“get”方法可能会更慢。例如,如果使用LinkedList,则会产生遍历开销,而for-each方法使用迭代器来跟踪其在列表中的位置。有关nuances of the for-each loop的更多信息。

我认为这篇文章现在在这里:new location

这里显示的链接已经死了。

答案 3 :(得分:10)

嗯,性能影响大多是微不足道的,但不是零。如果你看一下RandomAccess接口的JavaDoc:

  

根据经验,列表   实现应该实现这一点   接口if,对于典型的实例   这个循环:

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循环不是最快的。

答案 4 :(得分:5)

不幸的是,似乎存在差异。

如果你看两种循环的生成字节代码,它们是不同的。

以下是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 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。

答案 5 :(得分:4)

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

答案 6 :(得分:4)

foreach使您的代码意图更加清晰,并且通常优先于非常小的速度改进 - 如果有的话。

每当我看到一个索引循环时,我必须再解析一下,以确保它能够完成我认为它所做的事情。它是从零开始,是否包括或排除终点等?

我的大部分时间似乎花在阅读代码(我写的或其他人写的代码)上,而且清晰度几乎总是比性能更重要。这些天很容易解雇性能,因为Hotspot做了如此出色的工作。

答案 7 :(得分:4)

以下代码:

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 update 6运行Ubuntu 12.10 alpha。

一般情况下,HotSpot优化了很多间接操作和简单的reduntant操作,所以一般情况下你不应该担心它们,除非它们中有很多或者它们是嵌套的。

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

答案 8 :(得分:3)

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

答案 9 :(得分:3)

以下简要分析了Android开发团队的不同之处:

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

结果是 存在差异,而在非常有限的环境中,列表非常大,这可能是一个显着的差异。在他们的测试中,每个循环花费了两倍的时间。然而,他们的测试超过了400,000个整数的arraylist。数组中每个元素的实际差异为6 微秒。我没有经过测试,他们也没有说,但是我希望使用对象而不是原语稍微大一些差异,但即使你要构建库代码,你也不知道它的大小。你会被要求迭代,我认为差异不值得强调。

答案 10 :(得分:3)

唯一明确的方法是对其进行基准测试,即使是not as simple as it may sound。 JIT编译器可以对代码执行非常意外的操作。

答案 11 :(得分:2)

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

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

因此,除非循环中的逻辑需要索引,否则始终首选方法。

答案 12 :(得分:1)

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

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

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

答案 13 :(得分:1)

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

答案 14 :(得分:1)

接受的答案回答了问题,除了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;
    }
}

零()是最慢的,因为JIT还不能优化通过循环每次迭代获得一次数组长度的成本。

一个()更快。它将所有内容都拉到局部变量中,避免了查找。只有阵列长度才能提供性能优势。

对于没有JIT的设备,

two()最快,而对于具有JIT的设备,

两()无法区分。它使用Java编程语言1.5版中引入的增强型for循环语法。

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

答案 15 :(得分:-1)

public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}

答案 16 :(得分:-2)

是的,for-each变体比正常index-based-for-loop更快。

for-each变体使用iterator。因此,遍历比基于索引的普通for循环更快 这是因为iterator针对遍历进行了优化,因为它指向就在下一个元素之前和之后的元素之后。让index-based-for-loop变慢的一个原因是,它必须计算并每次移动到元素位置而不是iterator