ArrayList容器三种遍历元素方法的性能对比报告

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 list = new ArrayList();
        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 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 #411; //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 #471; //InterfaceMethod java/util/Iterator.hasNext:()Z
77:  ifne    55

Iterator方式:

1
2
3
4
5
6
7
108: aload   6
110: invokeinterface #411; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
115: checkcast   #20; //class java/lang/String
118: astore_2
119: aload   6
121: invokeinterface #471; //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 #652; //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循环,好处有两点:

  1. 速度快
  2. 有的情况下你是需要一个索引号的,而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方法。

 

参考资料:《ArrayList容器三种遍历元素方法的性能对比报告》 http://www.suselinks.us/performance-comparison-between-3-ways-to-traverse-an-arraylist

posted @ 2014-01-04 15:36  黎明就在眼前  阅读(485)  评论(1编辑  收藏  举报