以下两个循环之间的性能差异(如果有)是什么?
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();
}
Answers:
摘自Joshua Bloch的《有效Java》第46条:
版本1.5中引入的for-each循环通过完全隐藏迭代器或index变量,消除了混乱和出错的机会。结果成语同样适用于集合和数组:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
当您看到冒号(:)时,将其读为“ in”。因此,上面的循环读为“对于元素中的每个元素e”。请注意,即使对于数组,使用for-each循环也不会降低性能。实际上,在某些情况下,它可能只比普通的for循环提供一点性能优势,因为它只计算一次数组索引的限制。尽管您可以手动执行此操作(第45项),但程序员并非总是如此。
所有这些循环的作用完全相同,我只想在投入两美分之前先进行演示。
首先,循环遍历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) */ }
现在是实际的2美分:至少当我测试这些时,第3个是最快的,用毫秒数计算每种类型的循环所花的时间,用一次简单的操作重复几百万次-这是使用Java 5如果有人感兴趣,请在Windows上使用jre1.6u10。
虽然至少看起来是第三个是最快的,但您确实应该问自己:是否想冒风险在循环代码中的任何地方实施这种窥视孔优化的风险,因为从我所见,实际的循环是通常,这是任何实际程序中最耗时的部分(或者,也许我只是在错误的领域工作,谁知道)。就像我在Java for-each循环的前言中提到的(有些将其称为Iterator循环,另一些称为for-in循环)一样,使用它时,您不太可能遇到一个特定的错误。在讨论甚至比其他方法更快的速度之前,请记住javac根本没有优化字节码(嗯,几乎无论如何都没有),它只是对其进行编译。
但是,如果您要进行微优化,并且/或者您的软件使用了大量的递归循环,那么您可能会对第三种循环类型感兴趣。只需记住在更改for循环之前和之后都要对软件进行基准测试,您必须使用这种奇怪的,经过微优化的循环。
get(int)
,另一种使用Iterator
。考虑一下LinkedList
,for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
由于get(int)
n次执行,其性能要差得多。
通常应优先使用for-each循环。如果您使用的List实现不支持随机访问,则“获取”方法可能会比较慢。例如,如果使用LinkedList,则将产生遍历成本,而for-each方法使用迭代器来跟踪其在列表中的位置。有关for-each循环的细微差别的更多信息。
我认为文章现在在这里: 新位置
此处显示的链接已死。
嗯,对性能的影响几乎可以忽略不计,但并非为零。如果查看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
甲骨文怎么了?
我已经在Windows 7上使用Java 7和Java 8进行了尝试。
最好使用迭代器而不是建立索引。这是因为迭代器最有可能在List实现上被优化,而索引(调用get)则可能没有。例如,LinkedList是一个List,但是通过其元素进行索引比使用迭代器进行迭代要慢。
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
我正在运行带有OracleJDK 1.7更新6的Ubuntu 12.10 alpha。
通常,HotSpot优化了许多间接操作和简单的冗余操作,因此,除非它们中有很多是连续的或者它们被大量嵌套,否则通常不必担心它们。
另一方面,在LinkedList上建立索引的get比在LinkedList的迭代器上调用next慢得多,因此在使用迭代器时(在for-each循环中显式或隐式),可以在保持可读性的同时避免性能下降。
即使使用诸如ArrayList或Vector这样的东西,其中“ get”是一个简单的数组查找,第二个循环仍然具有第一个循环所没有的额外开销。我希望它会比第一个慢一点。
这是对Android开发团队提出的差异的简要分析:
https://www.youtube.com/watch?v=MZOf3pOAM6A
其结果是,存在的差异,并具有非常大的名单很内敛的环境中,它可能是一个明显的区别。在他们的测试中,for每个循环花费了两倍的时间。但是,他们的测试超过了400,000个整数的数组列表。数组中每个元素的实际差异为6 微秒。我还没有测试,他们也没有说,但是我希望使用对象而不是使用基元,差异会更大,但即使如此,除非您正在构建不知道将要问的内容多大的库代码进行迭代,我认为差异不值得强调。
除了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尚无法优化循环中每次迭代一次获取数组长度的成本。
one()更快。它将所有内容提取到局部变量中,从而避免了查找。只有阵列长度才能提供性能优势。
对于没有JIT的设备,two()最快,而对于具有JIT的设备,one()则无法区分。它使用Java编程语言1.5版中引入的增强的for循环语法。
因此,默认情况下应使用增强型for循环,但对于性能至关重要的ArrayList迭代,请考虑使用手写计数循环。
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+" ");
}
}
}