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的遍历速度。真正遍历的时候还是顺序的遍历,不会采用二分查找的。
ArrayList与LinkedList的普通for循环遍历的更多相关文章
- ArrayList和LinkedList的几种循环遍历方式及性能对比分析
最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性 ...
- Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]
Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...
- ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转)
主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论. 通过本文你可以 ...
- ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转载)
原文地址: http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 原文地址: http://www.trinea.cn ...
- 【转】ArrayList和LinkedList的几种循环遍历方式及性能对比分析
原文网址:http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 主要介绍ArrayList和LinkedList这两种 ...
- (转)ArrayList和LinkedList的几种循环遍历方式及性能对比分析
主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论. 通过本文你可以 ...
- To Java程序员:切勿用普通for循环遍历LinkedList
ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...
- ArrayList和LinkedList遍历方式及性能对比分析
ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayLis ...
- 切勿用普通for循环遍历LinkedList
ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...
随机推荐
- 一招搞定css页面布局
如何做出漂亮的页面: 1. 多写页面,多改. 2. 多写页面,多改. 3. 多写页面,多改. 大致的思想步骤: 写页面的时候先规划好大致的分块,无论是用定位或者浮动,首先要确定要应用的场景,使用完浮动 ...
- 10.vue router 带参数跳转
vue router 带参数跳转 发送:this.$router.push({path:'/news',query:{id:row.id}}) 接收:var id=this.$route.query. ...
- 史上最简单的SpringCloud教程 | 第十篇: 高可用的服务注册中心(Finchley版本)
转载请标明出处: 原文首发于 https://www.fangzhipeng.com/springcloud/2018/08/30/sc-f10-eureka/ 本文出自方志朋的博客 文章 史上最简单 ...
- Rem实现自适应布局
rem布局的目的是为了让我们可以用同一份代码,适应不同的移动终端(rem:就是css单位) 1.项目入口html文件<meta name="viewport" content ...
- MySQL常用参数说明(持续更新)
##innodb correlate innodb_flush_log_at_trx_commit value: 0,[1],2 effect: control the flush opera ...
- mariadb或者mysql忘记root密码
windows======================net stop mysql #先停止mysql或者在服务管理里面停止 直接打开Windows的命令行(CMD)窗口(以管理员身份运行),输入 ...
- MySQL数据库修改数据表类型(引擎)的方法
MySQL数据库使用事务,相关数据表必须为InnoDB引擎 查看数据表状态: SHOW TABLE STATUS FROM wawa WHERE NAME='ww_invite_code_temp'; ...
- vue实现多级弹窗
webpack + vue 实现 弹窗功能 对于刚入门webpack + vue 不久的新人来说,这技术,确实有些不太友好,相比较于直接操纵dom元素的jQuery,直接操纵数据的 vue 在webp ...
- 详解HTML5中的进度条progress元素简介及兼容性处理
一.progress元素基本了解 1.基本知识 progress元素属于HTML5家族,指进度条.IE10+以及其他靠谱浏览器都支持. 注释:Internet Explorer 9 以及更早的版本不支 ...
- Redis(七):Redis的发布订阅
Redis的发布订阅导航目录: 是什么 命令 案例 是什么 进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. 订阅/发布消息图 命令 案例 先订阅后发布后才能收到消息,1 ...