欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-07 22:33:15
java开发过程中,用到的最多的list集合就属arraylist与linkedlist。对于arraylist的遍历,通常是下面的方法: public stat...

java开发过程中,用到的最多的list集合就属arraylist与linkedlist。对于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,可能我们就会用相同得方法进行遍历,如下:

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;
}

 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弊端就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。