浅谈普通for循环遍历LinkedList弊端
java开发过程中,用到的最多的List集合就属ArrayList与LinkedList。对于ArrayList的遍历,通常是下面的方法:
publicstaticvoidmain(String[]args)
{
List<Integer>arrayList=
newArrayList<Integer>();
for(inti=0;i<100;i++)
arrayList.add(i);
for(inti=0;i<100;i++)
System.out.println(arrayList.get(i));
}
假如集合换成LinkedList,可能我们就会用相同得方法进行遍历,如下:
publicstaticvoidmain(String[]args)
{
List<Integer>linkedList=
newLinkedList<Integer>();
for(inti=0;i<100;i++)
linkedList.add(i);
for(inti=0;i<100;i++)
System.out.println(linkedList.get(i));
}
请记住:
下面对ArrayList和LinkedList的普通for循环效率进行测试以及分析原因。
ArrayList和LinkedList使用普通for循环遍历速度对比
先给出测试代码:
publicclassListIteratorTest
{
privatefinalstaticintLIST_SIZE=1000;
publicstaticvoidmain(String[]args)
{
List<Integer>arrayList=
newArrayList<Integer>();
List<Integer>linkedList=
newLinkedList<Integer>();
for(inti=0;i<LIST_SIZE;i++)
{
arrayList.add(i);
linkedList.add(i);
}
longstartTime=System.currentTimeMillis();
for(inti=0;i<arrayList.size();i++)
arrayList.get(i);
System.out.println("ArrayList遍历速度:"+(System.currentTimeMillis()-startTime)+"ms");
startTime=System.currentTimeMillis();
for(inti=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
ArrayList使用普通for循环遍历快的原因
先看一下ArrayList的get方法源代码:
publicEget(intindex){
RangeCheck(index);
return(E)elementData[index];
}
看到ArrayList的get方法只是从数组里面拿一个位置上的元素罢了。我们有结论,
其实熟悉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这块地址空间中读取数据出来。
整个过程,和数组有多大,并没有关系,计算机做的只是
LinkedList使用普通for循环遍历慢的原因
再看一下LinkedList的get方法做了什么:
publicEget(intindex){
returnentry(index).element;
}
Node<E>node(intindex){
//assertisElementIndex(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是双向链表,因此第4行的意思是算出i在一半前还是一半后,一半前正序遍历、一半后倒序遍历,这样会快很多,当然,先不管这个,分析一下为什么使用普通for循环遍历LinkedList会这么慢。
原因就在第6~第7行,第11~第12行的两个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里面的数据。
后面的以此类推。
也就是说,
时间复杂度有以下经验规则:
O(1)<O(log2N)<O(n)<O(N*log2N)<O(N2)<O(N3)<2N<3N<N!
前四个比较好、中间两个一般、后3个很烂。也就是说O(N2)是相对糟糕的一种时间复杂度了,N大一点,程序就会执行得比较慢。
后记
切记一定不要使用普通for循环去遍历LinkedList。使用迭代器或者foreach循环(foreach循环的原理就是迭代器)去遍历LinkedList即可,这种方式是直接按照地址去找数据的,将会大大提升遍历LinkedList的效率。
以上这篇浅谈普通for循环遍历LinkedList弊端就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。