ArrayList与LinkedList的普通for循环遍历

对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法:

public static void main(String[] args)
{
List<Integer> arrayList =
new ArrayList<Integer>(); for (int i = 0; i < 100; i++)
arrayList.add(i);
for (int i = 0; i < 100; i++)
System.out.println(arrayList.get(i));
}
 

如果以后要用到LinkedList了,可能有些朋友就会用一样的方式去遍历LinkedList了:

public static void main(String[] args)
{
List<Integer> linkedList =
new LinkedList<Integer>(); for (int i = 0; i < 100; i++)
linkedList.add(i);
for (int i = 0; i < 100; i++)
System.out.println(linkedList.get(i));
}

请记住:这是一种非常糟糕的做法。这其实已经不是Java的问题,而是数据结构的问题了,我相信语言从Java换成其他的也都一样。

下面对ArrayList和LinkedList的普通for循环效率进行测试以及分析原因。

ArrayList和LinkedList使用普通for循环遍历速度对比

先给出测试代码:

public class ArrayListAndLinkedList {

    private static final int LIST_SIZE = 1000;

    public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<Integer>();
List<Integer> linkedList = new LinkedList<Integer>(); for(int i = 0; i < LIST_SIZE; i++){
arrayList.add(i);
linkedList.add(i);
} long startTime = System.currentTimeMillis();
for(int i = 0; i < arrayList.size(); i++){
arrayList.get(i);
}
System.out.println("ArrayList的for循环遍历速度:" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis();
for(int i = 0; i < linkedList.size(); i++){
linkedList.get(i);
}
System.out.println("LinkedList的for循环遍历速度:" + (System.currentTimeMillis() - startTime) + "ms"); }

不断增大LIST_SIZE,我用表格表示一下运行结果:

  1000 5000 10000 50000 100000
ArrayList 0ms   1ms 2ms 3ms 3ms
LinkedList 3ms 16ms 88ms 2446ms 18848ms

从运行结果我们看到,按倍数增大List容量,ArrayList的遍历显得比较稳定,而LinkedList的遍历几乎是爆发式的增长,再测试下去已经没有必要了。

下面解释一下产生此现象的原因。

ArrayList使用普通for循环遍历快的原因

先看一下ArrayList的get方法源代码:

public E get(int index) {
rangeCheck(index); return elementData(index);
}

看到ArrayList的get方法只是从数组里面拿一个位置上的元素罢了。我们有结论,ArrayList的get方法的时间复杂度是O(1),O(1)的意思也就是说时间复杂度是一个常数,和数组的大小并没有关系,只要给定数组的位置,直接就能定位到数据。

其实熟悉C、C++或者对指针理解的朋友一定很好理解为什么,我解释一下为什么对数组使用get就快。

在计算机底层,数据都是有地址的,就像人有住址一样。假设我写了这么一句代码:

int[3] ints = {1, 3, 5};

在Java中一个int型数据是4个字节,此时计算机内部做的事情是,在内存空间中找到一块连续的、足以存放3个4字节也就是12字节的数组的内存空间,并返回该内存空间的首地址。比方说该内存空间的首地址是0x00吧,那么那么1就放在0x00~0x03中、3就放在0x04~0x07中、5就放在0x08~0x0B中。

这时就很简单了,取ints[1]的时候,计算机就会算出ints[1]的数据是存放在以0x04开头,占据4个字节空间的内存中,因此,计算机会从0x04~0x07这块地址空间中读取数据出来。

整个过程,和数组有多大,并没有关系,计算机做的只是算出起始地址-->去该地址中取数据而已,因此我们看到使用普通for循环遍历ArrayList的速度很快,也很稳定。

LinkedList使用普通for循环遍历慢的原因

再看一下LinkedList的get方法做了什么:

public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index); if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

由于LinkedList是双向链表,因此第6行的意思是算出i在一半前还是一半后,一半前正序遍历、一半后倒序遍历,这样会快很多,当然,先不管这个,分析一下为什么使用普通for循环遍历LinkedList会这么慢。

原因就在第7~第8行,第10~第11行的两个for循里面,以前者为例:

1、get(0),直接拿到0位的Node0的地址,拿到Node0里面的数据

2、get(1),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,拿到Node1里面的数据

3、get(2),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,从1位的Node1中找到下一个2位的Node2的地址,找到Node2,拿到Node2里面的数据。

后面的以此类推。

也就是说,LinkedList在get任何一个位置的数据的时候,都会把前面的数据走一遍。假如我有10个数据,那么将要查询1+2+3+4+5+5+4+3+2+1=30次数据,相比ArrayList,却只需要查询10次数据就行了,随着LinkedList的容量越大,差距会越拉越大。其实使用LinkedList到底要查询多少次数据,大家应该已经很明白了,来算一下:按照前一半算应该是(1 + 0.5N) * 0.5N / 2,后一半算上即乘以2,应该是(1 + 0.5N) * 0.5N ,忽略低阶项和首项系数,得出结论,LinikedList遍历的时间复杂度为O(N*N)N为LinkedList的容量

时间复杂度有以下经验规则:

O(1) < O(log2N) < O(n) < O(N * log2N) < O(N2) < O(N3) < 2N < 3N < N!

前四个比较好、中间两个一般、后3个很烂。也就是说O(N2)是相对糟糕的一种时间复杂度了,N大一点,程序就会执行得比较慢。

根据以上的分析,各位Java程序员朋友们,切记一定不要使用普通for循环去遍历LinkedList。使用迭代器或者foreach循环(foreach循环的原理就是迭代器)去遍历LinkedList即可,这种方式是直接按照地址去找数据的,将会大大提升遍历LinkedList的效率。

另注:LinkedList的遍历,因为它是双向链表,只是说index在前一半和后一半,分别采用正序遍历和倒序遍历以加快LinkedList的遍历速度。真正遍历的时候还是顺序的遍历,不会采用二分查找的。

不要用for循环去遍历LinkedList的更多相关文章

  1. To Java程序员:切勿用普通for循环遍历LinkedList

    ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...

  2. 切勿用普通for循环遍历LinkedList

    ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...

  3. 普通for循环遍历LinkedList弊端

    java开发过程中,用到的最多的List集合就属ArrayList与LinkedList.对于ArrayList的遍历,通常是下面的方法: public static void main(String ...

  4. To Java程序员:切勿用普通for循环遍历LinkedList(转)

    ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...

  5. 为什么LinkedList不建议使用for循环遍历,而使用iterator方式进行遍历,但ArrayList建议使用for循环进行遍历呢?

    如果使用for循环方式遍历链表,由于链表中元素是通过指针连接彼此的,不存在索引的概念,如果使用for循环方式遍历LinkedList,依次传入索引值,则就相当于每次都要将链表撸一遍. 如:在下面的这个 ...

  6. python 如何在一个for循环中遍历两个列表

    是我在看<笨方法学python>过程中发现有一行代码看不懂--" for sentence in snippet, phrase:",所以研究了半天,感觉挺有收获的.所 ...

  7. bboss oreach循环嵌套遍历map

    foreach循环嵌套遍历mapforeach嵌套dsl脚本定义 <property name="dynamicInnerDsl"> <![CDATA[{ ## ...

  8. JS去遍历Table的所有单元格中的内容

    用JS去遍历Table的所有单元格中的内容,可以用如下JS代码实现 function GetInfoFromTable(tableid) { var tableInfo = ""; ...

  9. 【VBA】Do While……Loop循环,遍历某列

    [说明] Do While……Loop循环,遍历某列 i = Do While Trim(T_sh.Cells(i, NOTESID_COL)) <> "" If T_ ...

随机推荐

  1. hihoCoder #1498 : Diligent Robots【数学】

    #1498 : Diligent Robots 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 There are N jobs to be finished. It t ...

  2. HDU 2147 kiki's game(规律,博弈)

    kiki's game Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 40000/10000 K (Java/Others)Total ...

  3. [51nod1329]路径游戏

    Snuke与Sothe两个人在玩一个游戏.游戏在一个2*N的网格中进行(2行N列),这个网格中的2N个格子不是黑色就是白色.定义,一条有效路径是指一个完全由白色格子构成的序列,这个序列的第一个网格元素 ...

  4. 洛谷 P1219 八皇后【经典DFS,温习搜索】

    P1219 八皇后 题目描述 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 上面的布局可以用序 ...

  5. JDBC连接数据库(查询)的步骤

    先导入jar包 代码: import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;impor ...

  6. [国嵌攻略][119][Linux中断处理程序设计]

    裸机中断: 1.中断统一入口. 2.注册中断处理程序. 3.根据中断源编号,调用中断处理程序. Linux中断 1.在entry-armv.S中的_irq_svc是中断统一入口. 2.获取产生中断源的 ...

  7. Servlet&&Jsp 概述

    主题 Servlet的作用 构建动态网页 Servlet代码初探 Servlet与其他技术的对比 Jsp的作用 Servlet的作用 Servlet是在web服务器或应用服务器上用来动态生成html的 ...

  8. Lytro 光场相机重对焦C++实现以及CUDA实现

    前面有几篇博客主要介绍了光场和光场相机相关知识,以及重对焦效果和多视角效果的展示.算是自己学习光场过程的一种总结. 这次贴上自己用OpenCV/C++编写的重对焦算法实现(包含CPU版和CUDA GP ...

  9. 利用光场进行深度图估计(Depth Estimation)算法之一——聚焦算法

    前面几篇博客主要说了光场相机,光场相机由于能够记录相机内部整个光场,可以实现重聚焦(模糊线索)和不同视角的变换(视差线索),同时也可以利用这个特性进行深度估计(Depth Estimation). 先 ...

  10. js双向绑定和地址传递带来的痛苦解决方案之对象拷贝

    function cloneObj(obj) { var newObj = {}; if (obj instanceof Array) { newObj = []; } for (var key in ...