最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析

主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论
通过本文你可以了解(1)List的五种遍历方式及各自性能 (2)foreach及Iterator的实现 (3)加深对ArrayList和LinkedList实现的了解。
阅读本文前希望你已经了解ArrayList顺序存储和LinkedList链式的结构,本文不对此进行介绍。

1. List的五种遍历方式
下面只是简单介绍各种遍历示例(以ArrayList为例),各自优劣会在本文后面进行分析给出结论。
(1) for each循环

 

Java

 
1
2
3
4
List<Integer>list=newArrayList<Integer>();
for(Integerj:list){
    // use j
}

(2) 显示调用集合迭代器

 

Java

 
1
2
3
4
List<Integer>list=newArrayList<Integer>();
for(Iterator<Integer>iterator=list.iterator();iterator.hasNext();){
    iterator.next();
}

 

Java

 
1
2
3
4
5
List<Integer>list=newArrayList<Integer>();
Iterator<Integer>iterator=list.iterator();
while(iterator.hasNext()){
    iterator.next();
}

(3) 下标递增循环,终止条件为每次调用size()函数比较判断

 

Java

 
1
2
3
4
List<Integer>list=newArrayList<Integer>();
for(intj=0;j<list.size();j++){
    list.get(j);
}

(4) 下标递增循环,终止条件为和等于size()的临时变量比较判断

 

Java

 
1
2
3
4
5
List<Integer>list=newArrayList<Integer>();
intsize=list.size();
for(intj=0;j<size;j++){
    list.get(j);
}

(5) 下标递减循环

 

Java

 
1
2
3
4
List<Integer>list=newArrayList<Integer>();
for(intj=list.size()-1;j>=0;j--){
    list.get(j);
}

在测试前大家可以根据对ArrayList和LinkedList数据结构及Iterator的了解,想想上面五种遍历方式哪个性能更优。

2、List五种遍历方式的性能测试及对比
以下是性能测试代码,会输出不同数量级大小的ArrayList和LinkedList各种遍历方式所花费的时间。

ArrayList和LinkedList循环性能对比测试代码

 

PS:如果运行报异常in thread “main” java.lang.OutOfMemoryError: Java heap space,请将main函数里面list size的大小减小。

其中getArrayList函数会返回不同size的ArrayList,getLinkedList函数会返回不同size的LinkedList。
loopListCompare函数会分别用上面的遍历方式1-5去遍历每一个list数组(包含不同大小list)中的list。
print开头函数为输出辅助函数。

测试环境为Windows7 32位系统 3.2G双核CPU 4G内存,Eclipse -Xms512m -Xmx512m
最终测试结果如下:

 
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
compare loop performance of ArrayList
--------------------------------------------------------------
list size             |10000   |100000  |1000000|10000000
--------------------------------------------------------------
foreach              |1ms    |3ms    |14ms   |154ms  
--------------------------------------------------------------
foriterator          |0ms    |1ms    |12ms   |115ms  
--------------------------------------------------------------
forlist.size()       |1ms    |1ms    |13ms   |128ms  
--------------------------------------------------------------
forsize=list.size()|0ms    |1ms    |6ms    |62ms  
--------------------------------------------------------------
forj--               |0ms    |1ms    |7ms    |62ms  
--------------------------------------------------------------
 
compare loop performance of LinkedList
--------------------------------------------------------------
list size             |100     |1000    |10000   |100000  
--------------------------------------------------------------
foreach              |0ms    |0ms    |1ms    |3ms    
--------------------------------------------------------------
foriterator          |0ms    |0ms    |0ms    |2ms    
--------------------------------------------------------------
forlist.size()       |0ms    |0ms    |71ms   |7841ms
--------------------------------------------------------------
forsize=list.size()|1ms    |0ms    |69ms   |7874ms
--------------------------------------------------------------
forj--               |0ms    |0ms    |68ms   |7664ms
--------------------------------------------------------------

第一张表为ArrayList对比结果,第二张表为LinkedList对比结果。

表横向为同一遍历方式不同大小list遍历的时间消耗,纵向为同一list不同遍历方式遍历的时间消耗。
PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

3、遍历方式性能测试结果分析
(1) foreach介绍
foreach是Java SE5.0引入的功能很强的循环结构,for (Integer j : list)应读作for each int in list。
for (Integer j : list)实现几乎等价于

 

Java

 
1
2
3
4
Iterator<Integer>iterator=list.iterator();
while(iterator.hasNext()){
    Integerj=iterator.next();
}

下面的分析会将foreach和显示调用集合迭代器两种遍历方式归类为Iterator方式,其他三种称为get方式遍历。

这时我们已经发现foreach的一大好处,简单一行实现了四行的功能,使得代码简洁美观,另一大好处是相对于下标循环而言的,foreach不必关心下标初始值和终止值及越界等,所以不易出错Effective-Java中推荐使用此种写法遍历,本文会验证这个说法。

使用foreach结构的类对象必须实现了Iterable接口,Java的Collection继承自此接口,List实现了Collection,这个接口仅包含一个函数,源码如下:

 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
packagejava.lang;
 
importjava.util.Iterator;
 
/**
* Implementing this interface allows an object to be the target of
* the "foreach" statement.
*
* @param <T> the type of elements returned by the iterator
*
* @since 1.5
*/
publicinterfaceIterable<T>{
 
    /**
     * Returns an iterator over a set of elements of type T.
     *
     * @return an Iterator.
     */
    Iterator<T>iterator();
}

iterator()用于返回一个Iterator,从foreach的等价实现中我们可以看到,会调用这个函数得到Iterator,再通过Iterator的next()得到下一个元素,hasNext()判断是否还有更多元素。Iterator源码如下:

 

Java

 
1
2
3
4
5
6
7
publicinterfaceIterator<E>{
    booleanhasNext();
 
    Enext();
 
    voidremove();
}

(2) ArrayList遍历方式结果分析

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
compare loop performance of ArrayList
--------------------------------------------------------------
list size             |10000   |100000  |1000000|10000000
--------------------------------------------------------------
foreach              |1ms    |3ms    |14ms   |154ms  
--------------------------------------------------------------
foriterator          |0ms    |1ms    |12ms   |115ms  
--------------------------------------------------------------
forlist.size()       |1ms    |1ms    |13ms   |128ms  
--------------------------------------------------------------
forsize=list.size()|0ms    |1ms    |6ms    |62ms  
--------------------------------------------------------------
forj--               |0ms    |1ms    |7ms    |62ms  
--------------------------------------------------------------

PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

从上面我们可以看出:
a. 在ArrayList大小为十万之前,五种遍历方式时间消耗几乎一样
b. 在十万以后,第四、五种遍历方式快于前三种,get方式优于Iterator方式,并且

 

Java

 
1
2
3
4
intsize=list.size();
for(intj=0;j<size;j++){
    list.get(j);
}

用临时变量size取代list.size()性能更优。我们看看ArrayList中迭代器Iterator和get方法的实现

 

Java

 
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
privateclassItrimplementsIterator<E>{
    intcursor;       // index of next element to return
    intlastRet=-1;// index of last element returned; -1 if no such
    intexpectedModCount=modCount;
 
    publicbooleanhasNext(){
        returncursor!=size;
    }
 
    @SuppressWarnings("unchecked")
    publicEnext(){
        checkForComodification();
        inti=cursor;
        if(i>=size)
            thrownewNoSuchElementException();
        Object[]elementData=ArrayList.this.elementData;
        if(i>=elementData.length)
            thrownewConcurrentModificationException();
        cursor=i+1;
        return(E)elementData[lastRet=i];
    }
    ……
}
 
publicEget(intindex){
    rangeCheck(index);
 
    returnelementData(index);
}

从中可以看出get和Iterator的next函数同样通过直接定位数据获取元素,只是多了几个判断而已。

c . 从上可以看出即便在千万大小的ArrayList中,几种遍历方式相差也不过50ms左右,且在常用的十万左右时间几乎相等,考虑foreach的优点,我们大可选用foreach这种简便方式进行遍历。

(3) LinkedList遍历方式结果分析

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
compare loop performance of LinkedList
--------------------------------------------------------------
list size             |100     |1000    |10000   |100000  
--------------------------------------------------------------
foreach              |0ms    |0ms    |1ms    |3ms    
--------------------------------------------------------------
foriterator          |0ms    |0ms    |0ms    |2ms    
--------------------------------------------------------------
forlist.size()       |0ms    |0ms    |71ms   |7841ms
--------------------------------------------------------------
forsize=list.size()|1ms    |0ms    |69ms   |7874ms
--------------------------------------------------------------
forj--               |0ms    |0ms    |68ms   |7664ms
--------------------------------------------------------------

PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

从上面我们可以看出:
a 在LinkedList大小接近一万时,get方式和Iterator方式就已经差了差不多两个数量级,十万时Iterator方式性能已经远胜于get方式。
我们看看LinkedList中迭代器和get方法的实现

 

Java

 
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
45
46
47
48
49
50
51
52
privateclassListItrimplementsListIterator<E>{
    privateNode<E>lastReturned=null;
    privateNode<E>next;
    privateintnextIndex;
    privateintexpectedModCount=modCount;
 
    ListItr(intindex){
        // assert isPositionIndex(index);
        next=(index==size)?null:node(index);
        nextIndex=index;
    }
 
    publicbooleanhasNext(){
        returnnextIndex<size;
    }
 
    publicEnext(){
        checkForComodification();
        if(!hasNext())
            thrownewNoSuchElementException();
 
        lastReturned=next;
        next=next.next;
        nextIndex++;
        returnlastReturned.item;
    }
    ……
}
 
publicEget(intindex){
    checkElementIndex(index);
    returnnode(index).item;
}
 
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E>node(intindex){
    // assert isElementIndex(index);
 
    if(index<(size>>1)){
        Node<E>x=first;
        for(inti=0;i<index;i++)
            x=x.next;
        returnx;
    }else{
        Node<E>x=last;
        for(inti=size-1;i>index;i--)
            x=x.prev;
        returnx;
    }
}

从上面代码中可以看出LinkedList迭代器的next函数只是通过next指针快速得到下一个元素并返回。而get方法会从头遍历直到index下标,查找一个元素时间复杂度为哦O(n),遍历的时间复杂度就达到了O(n2)。

所以对于LinkedList的遍历推荐使用foreach,避免使用get方式遍历。

(4) ArrayList和LinkedList遍历方式结果对比分析
从上面的数量级来看,同样是foreach循环遍历,ArrayList和LinkedList时间差不多,可将本例稍作修改加大list size会发现两者基本在一个数量级上。
但ArrayList get函数直接定位获取的方式时间复杂度为O(1),而LinkedList的get函数时间复杂度为O(n)。
再结合考虑空间消耗的话,建议首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。

4、结论总结
通过上面的分析我们基本可以总结下:
(1) 无论ArrayList还是LinkedList,遍历建议使用foreach,尤其是数据量较大时LinkedList避免使用get遍历。
(2) List使用首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。
(3) 可能在遍历List循环内部需要使用到下标,这时综合考虑下是使用foreach和自增count还是get方式。

你可能还感兴趣:

Android性能优化系列总篇

性能优化之Java(Android)代码优化

Android公共库(缓存 下拉ListView 下载管理Pro 静默安装 root运行 Java公共类)

Android ImageCache图片缓存

最新文章

  1. CentOS下Zabbix安装部署及汉化
  2. TSP旅行商问题的Hopfield求解过程
  3. 【AngularJS】—— 12 独立作用域
  4. node-webkit教程(10)Platform Service之File dialogs
  5. html5添加音乐包括暂停
  6. Winfrom 基于TCP的Socket 编程
  7. 轻松学习Linux之认识Shell
  8. 学习Ember遇到的一些问题
  9. CodeForces Round #286 Div.2
  10. c语言函数注意点
  11. crm2011i创建nt类型字段
  12. 针对不同手机系统的LBS地图定位解决方案
  13. 导入网页数据到 Google Sheet
  14. 表单Checkbox全选反选全不选
  15. 【转】mysql 中int类型字段unsigned和signed的区别
  16. asp.net mvc简单实现基于Razor的分页控件
  17. ECMA Script 6_字符串_扩展_字符 是4字节还是2字节?_模板字符串
  18. 2.2JAVA基础复习——JAVA语言的基础组成运算符和语句
  19. 处理机调度算法( RR 、HRRF)
  20. bash中的pasue

热门文章

  1. poj 2051.Argus 解题报告
  2. oracle简历自增序列(转)
  3. php继承、多态
  4. 在CMD窗口中使用javac和java命令进行编译和执行带有包名的具有继承关系的类
  5. September 6th 2016 Week 37th Tuesday
  6. SQL语句删除重复数据
  7. hdu1018(数位)
  8. JAVA作业 字符变整型相加,整型输出
  9. echarts基本使用
  10. .net学习笔记---webconfig的读与写