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 ListIteratorTest
{private final static 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遍历速度:" + (System.currentTimeMillis() - startTime) + "ms");startTime = System.currentTimeMillis();for (int i = 0; i < linkedList.size(); i++)        {linkedList.get(i);        }System.out.println("LinkedList遍历速度:" + (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 (E) 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) {return entry(index).element;
}
 1 private Entry<E> entry(int index) {
 2     if (index < 0 || index >= size)
 3         throw new IndexOutOfBoundsException("Index: "+index+
 4                                             ", Size: "+size);
 5     Entry<E> e = header;
 6     if (index < (size >> 1)) {
 7         for (int i = 0; i <= index; i++)
 8             e = e.next;
 9     } else {
10         for (int i = size; i > index; i--)
11             e = e.previous;
12     }
13     return e;
14 }

由于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 = 0.25N2 + 0.5N,忽略低阶项和首项系数,得出结论,LinikedList遍历的时间复杂度为O(N2),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的效率。

from: http://www.cnblogs.com/xrq730/p/5189565.html

To Java程序员:切勿用普通for循环遍历LinkedList相关推荐

  1. Java程序员谈一谈-----java程序员成长之路

    转载:http://www.banzg.com/archives/679.html?ref=myread 阿里面试回来,想和Java程序员谈一谈 引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力 ...

  2. 转载一篇不错的文章:谈谈“野生”Java程序员学习的道路

    逛论坛看到的一篇不错的文章,特此转载:本文转载自:左潇龙  原文链接:点击打开链接 引言 很尴尬的是,这个类型的文章其实之前笔者就写过,原文章里,笔者自称LZ(也就是楼主,有人说是老子的简写,笔者只想 ...

  3. java原生的编译软件_原生态Java 程序员容易忽视的编程细节

    Java是Java程序设计语言和Java平台的总称,要想学好一门语言,打好基础最关键的,学习一种新的编程语言比学习新的口头语言要容易得多.然而,在这两种学习过程中,都要付出额外的努力去学习不带口音地说 ...

  4. java大师指导,野生java程序员生存之路

    很尴尬的是,这个类型的文章其实之前笔者就写过,原文章里,笔者自称LZ(也就是楼主,有人说是老子的简写,笔者只想说,这位同学你站出来,保证不打死你,-_-),原文章名称叫做<回答阿里社招面试如何准 ...

  5. 从阿里社招面试,看“野生”Java程序员的学习道路

    引言 很尴尬的是,这个类型的文章其实之前笔者就写过,原文章里,笔者自称LZ(也就是楼主,有人说是老子的简写,笔者只想说,这位同学你站出来,保证不打死你,-_-),原文章名称叫做<回答阿里社招面试 ...

  6. 阿里面试回来,想和Java程序员谈一谈

    阿里面试回来,想和Java程序员谈一谈 引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后, ...

  7. java web 显示项目下的图片_[适合初中级Java程序员修炼手册从0搭建整个Web项目](一)...

    前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206种一棵树最好的时间是十年前,其次是现在 six-finger-web 一个Web后端 ...

  8. Java程序员如何做到Java架构师

    动力节点Java培训最新上线Java实验班,等你来测试自己适不适合学习Java编程哦! 裁员的企业不分大小,有市值百亿的上市公司,也有成立不到一年的创业新星:被裁的员工不论资历,有试用期未满的懵懂新人 ...

  9. apache为什么更适合处理动态请求_[适合初中级Java程序员修炼手册从0搭建整个Web项目](一)...

    前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206种一棵树最好的时间是十年前,其次是现在 six-finger-web 一个Web后端 ...

最新文章

  1. oracle 事务_从Oracle到PG-PostgreSQL的MVCC机制简介
  2. BugkuCTF-WEB题你从哪里来
  3. 三星CEO承认:可折叠智能手机Galaxy Fold开卖得太着急
  4. 【Redis】redis-3.0.0安装以及集群的搭建
  5. matlab2c使用c++实现matlab函数系列教程-sum函数
  6. 多线程中的互斥控制程序代码_互斥锁解决 Python 中多线程共享全局变量的问题...
  7. 傅里叶变换的相关实验——matlab实现
  8. 一个简单的Python暴力破解网站登录密码脚本
  9. udp接受_电脑网络基础知识:用户数据报协议(UDP)的学习
  10. python怎么加逗号_Python 逗号的巧用
  11. 计算机保存途径如何更改,如何修改“我的文档”路径方便保存文件
  12. Crackme#1算法注册机
  13. 2017最新安卓版UI设计规范篇
  14. android自动对焦第一次对焦,Android自定义相机实现自动对焦和手动对焦
  15. Win7系统便签怎样设置一个闹钟提醒
  16. java 交易金额转换分,java金额元与分转换工具种
  17. 关于谷歌浏览器默认首页为好123的解决办法
  18. 自制jlink-ob
  19. Java并发编程艺术阅读笔记(一)
  20. av_register_all

热门文章

  1. weblogic升级之ddconverter
  2. 解决 ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)异常
  3. mysql的引擎讲解
  4. 大数据分析工资单:六大行员工再涨薪 人均年薪超26万
  5. CAFFE学习笔记(一)Caffe_Example之训练mnist
  6. ICML论文|这违反直觉的“升噪”方法,反而能很好的解决激活函数梯度弥散的问题
  7. 淘宝内部分享:MySQL MariaDB性能优化
  8. 实战并发编程 - 02解决并发问题常用套路
  9. Spring4.X系列之AOP基础篇
  10. 找不到jar包的解决方法