gpt4 book ai didi

浅谈普通for循环遍历LinkedList弊端

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 25 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章浅谈普通for循环遍历LinkedList弊端由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

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

?
1
2
3
4
5
6
7
8
9
10
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,可能我们就会用相同得方法进行遍历,如下:

?
1
2
3
4
5
6
7
8
9
10
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循环遍历速度对比 。

先给出测试代码:

?
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
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方法源代码:

?
1
2
3
4
5
public E get( int index) {
RangeCheck(index);
 
return (E) elementData[index];
}

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

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

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

?
1
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方法做了什么:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public E get( int index) {
   return entry(index).element;
}
 
  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是双向链表,因此第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里面的数据.

后面的以此类推.

也就是说,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大一点,程序就会执行得比较慢.

后记 。

切记一定不要使用普通for循环去遍历LinkedList。使用迭代器或者foreach循环(foreach循环的原理就是迭代器)去遍历LinkedList即可,这种方式是直接按照地址去找数据的,将会大大提升遍历LinkedList的效率.

以上这篇浅谈普通for循环遍历LinkedList弊端就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我.

最后此篇关于浅谈普通for循环遍历LinkedList弊端的文章就讲到这里了,如果你想了解更多关于浅谈普通for循环遍历LinkedList弊端的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com