上周有同事问我ArrayList容器中for循环、ForEach循环和iterator迭代哪种性能更好。其实在此之前我是没有测试过的,所以我想当然地认为,既然iterator方式需要先获取一个iterator,性能应该要差一些。
没有验证过的结论,如鲠在喉。
终于我还是写了下面这个小程序,来验证我的想法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
import
java
.
util
.
ArrayList
;
import
java
.
util
.
Iterator
;
import
java
.
util
.
LinkedList
;
import
java
.
util
.
List
;
public
class
ForEachIteratorForPerformanceTest
{
public
static
void
main
(
String
[
]
args
)
{
int
loop_count
=
100000
;
String
test
;
List
<String>
list
=
new
ArrayList
<String>
(
)
;
for
(
int
i
=
0
;
i
<
loop_count
;
i
++
)
{
list
.
add
(
String
.
valueOf
(
i
)
)
;
}
long
start
=
System
.
nanoTime
(
)
;
for
(
String
s
:
list
)
{
test
=
s
;
}
System
.
out
.
println
(
System
.
nanoTime
(
)
-
start
)
;
start
=
System
.
nanoTime
(
)
;
for
(
Iterator
<String>
it
=
list
.
iterator
(
)
;
it
.
hasNext
(
)
;
)
{
test
=
it
.
next
(
)
;
}
System
.
out
.
println
(
System
.
nanoTime
(
)
-
start
)
;
start
=
System
.
nanoTime
(
)
;
int
size
=
list
.
size
(
)
;
for
(
int
i
=
0
;
i
<
size
;
i
++
)
{
test
=
list
.
get
(
i
)
;
}
System
.
out
.
println
(
System
.
nanoTime
(
)
-
start
)
;
}
}
|
结果大出我的意料之外。
1
2
3
|
6582505
//ForEach方式遍历大小为十万的ArrayList消耗的时间,单位纳秒,大约6.5毫秒
4596135
//Iterator方式遍历大小为十万的ArrayList消耗的时间,单位纳秒,大约4.5毫秒
1522557
//For方式遍历大小为十万的ArrayList消耗的时间,单位纳秒,大约1.5毫秒
|
从上面的测试结果来看,for方式几乎是iterator方式的3倍,而iterator方式几乎是foreach的1.5倍!
秉着探究事实真相的精神,我们来分析下这到底是怎么一回事。
先来看foreach和iterator的差异性。下面是使用javap命令把java的字节码反编译后的Java虚拟机规范定义的字节代码指令,我们来对比下它们的差异性。
ForEach方式:
1
2
3
4
5
6
7
8
9
|
55
:
aload
7
57
:
invokeinterface
#
41
,
1
;
//InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
62
:
checkcast
#
20
;
//class java/lang/String
65
:
astore
6
67
:
aload
6
69
:
astore_2
70
:
aload
7
72
:
invokeinterface
#
47
,
1
;
//InterfaceMethod java/util/Iterator.hasNext:()Z
77
:
ifne
55
|
Iterator方式:
1
2
3
4
5
6
7
|
108
:
aload
6
110
:
invokeinterface
#
41
,
1
;
//InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
115
:
checkcast
#
20
;
//class java/lang/String
118
:
astore_2
119
:
aload
6
121
:
invokeinterface
#
47
,
1
;
//InterfaceMethod java/util/Iterator.hasNext:()Z
126
:
ifne
108
|
可以看出,foreach循环竟然是用iterator实现的,除了高亮标注的第4、5行外,其它的指令和显式使用iterator方式一模一样。令人不解的是,foreach循环竟然还比显式使用iterator的方式多出了两条指令。看来这就是foreach循环比iterator迭代慢的原因了。jdk5引入的这一新特性除了让代码看起来简洁外,并没有什么高明之处。有点小失望。
再来看看for循环的字节代码指令:
1
2
3
4
5
6
7
8
9
|
160
:
aload_3
161
:
iload
7
163
:
invokeinterface
#
65
,
2
;
//InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
168
:
checkcast
#
20
;
//class java/lang/String
171
:
astore_2
172
:
iinc
7
,
1
175
:
iload
7
177
:
iload
6
179
:
if_icmplt
160
|
foreach和iterator方式的循环体中有两条invokeinterface的指令;而for循环中只有一条invokeinterface指令,其余的都是堆栈操作,而堆栈操作是快于invokeinterface操作的。这也是for循环快于iterator的原因。
这么看来,要遍历ArrayList中的元素,还是首推for循环,好处有两点:
- 速度快
- 有的情况下你是需要一个索引号的,而for循环默认就提供了,iterator和foreach还得额外定义变量。
当然,上面的例子中for循环快于iterator是有条件限制的。还是拿上述例子来说,for循环中调用的是list.get方法,而该list是ArrayList,get方法其实就是根据数字下标获取对象。ArrayList的读取是非常快的,就那么几条汇编指令,时间复杂度是o(1)。但是如果把ArrayList换成LinkedList,for循环就会比iterator慢上2 ~ 3个数量级了。原因就在于linkedlist的get方法调用每次都要遍历一遍LinkedList,时间复杂度是o(n)。当然,罪魁祸首是LinkedList.get方法。我贴一个测试结果给你看就明白了。
1
2
3
|
2359660
//ForEach方式遍历大小为一万的LinkedList消耗的时间,单位纳秒
284634
//Iterator方式遍历大小为一万的LinkedList消耗的时间,单位纳秒
182291581
//For方式遍历大小为一万的LinkedList消耗的时间,单位纳秒
|
因此,如果要遍历LinkedList容器中的元素,最好还是显式的使用iterator方法。