ArrayList和LinkedList

ArrayList和LinkedList的区别

ArrayListVector使用了数组的实现,可以认为ArrayList或者Vector封装了对内部数组的操作,比如向数组中添加,删除,插入新的元素或者数据的扩展和重定向。

LinkedList使用了循环双向链表数据结构。与基于数组ArrayList相比,这是两种截然不同的实现技术,这也决定了它们将适用于完全不同的工作场景。

LinkedList链表由一系列表项连接而成。一个表项总是包含3个部分:元素内容,前驱表和后驱表,如图所示:

https://i-blog.csdnimg.cn/blog_migrate/6ce1a7cba47850909364f3c86c593200.jpeg

在下图展示了一个包含3个元素的LinkedList的各个表项间的连接关系。在JDK的实现中,无论LikedList是否为空,链表内部都有一个header表项,它既表示链表的开始,也表示链表的结尾。表项header的后驱表项便是链表中第一个元素,表项header的前驱表项便是链表中最后一个元素。

https://i-blog.csdnimg.cn/blog_migrate/b649450fed2a08d39f6f33979c7beb0f.jpeg 

下面以增加和删除元素为例比较ArrayListLinkedList的不同之处:

1)增加元素到列表尾端:

ArrayList中增加元素到队列尾端的代码如下:

public boolean add(E e){

   ensureCapacity(size+1);//确保内部数组有足够的空间

   elementData[size++]=e;//将元素加入到数组的末尾,完成添加

   return true;     

}

 ArrayListadd()方法的性能决定于ensureCapacity()方法。ensureCapacity()的实现如下:

public vod ensureCapacity(int minCapacity){

  modCount++;

  int oldCapacity=elementData.length;

  if(minCapacity>oldCapacity){    //如果数组容量不足,进行扩容

      Object[] oldData=elementData;

      int newCapacity=(oldCapacity*3)/2+1;  //扩容到原始容量的1.5倍

      if(newCapacitty<minCapacity)   //如果新容量小于最小需要的容量,则使用最小

                                                    //需要的容量大小

         newCapacity=minCapacity ;  //进行扩容的数组复制

         elementData=Arrays.copyof(elementData,newCapacity);

  }

}

可以看到,只要ArrayList的当前容量足够大,add()操作的效率非常高的。只有当ArrayList对容量的需求超出当前数组大小时,才需要进行扩容。扩容的过程中,会进行大量的数组复制操作。而数组复制时,最终将调用System.arraycopy()方法,因此add()操作的效率还是相当高的。

LinkedList add()操作实现如下,它也将任意元素增加到队列的尾端:

public boolean add(E e){

   addBefore(e,header);//将元素增加到header的前面

   return true;

}

其中addBefore()的方法实现如下:

private Entry<E> addBefore(E e,Entry<E> entry){

     Entry<E> newEntry = new Entry<E>(e,entry,entry.previous);

     newEntry.provious.next=newEntry;

     newEntry.next.previous=newEntry;

     size++;

     modCount++;

     return newEntry;

}

可见,LinkeList由于使用了链表的结构,因此不需要维护容量的大小。从这点上说,它比ArrayList有一定的性能优势,然而,每次的元素增加都需要新建一个Entry对象,并进行更多的赋值操作。在频繁的系统调用中,对性能会产生一定的影响。

2)增加元素到列表任意位置

除了提供元素到List的尾端,List接口还提供了在任意位置插入元素的方法:void add(int index,E element);

由于实现的不同,ArrayListLinkedList在这个方法上存在一定的性能差异,由于ArrayList是基于数组实现的,而数组是一块连续的内存空间,如果在数组的任意位置插入元素,必然导致在该位置后的所有元素需要重新排列,因此,其效率相对会比较低。

以下代码是ArrayList中的实现:

public void add(int index,E element){

   if(index>size||index<0)

      throw new IndexOutOfBoundsException(

        "Index:"+index+",size: "+size);

         ensureCapacity(size+1);

         System.arraycopy(elementData,index,elementData,index+1,size-index);

         elementData[index] = element;

         size++;

可以看到每次插入操作,都会进行一次数组复制。而这个操作在增加元素到List尾端的时候是不存在的,大量的数组重组操作会导致系统性能低下。并且插入元素在List中的位置越是靠前,数组重组的开销也越大。

LinkedList此时显示了优势:

public void add(int index,E element){

   addBefore(element,(index==size?header:entry(index)));

}

可见,对LinkedList来说,在List的尾端插入数据与在任意位置插入数据是一样的,不会因为插入的位置靠前而导致插入的方法性能降低。

3)删除任意位置元素

对于元素的删除,List接口提供了在任意位置删除元素的方法:

public E remove(int index);

ArrayList来说,remove()方法和add()方法是雷同的。在任意位置移除元素后,都要进行数组的重组。ArrayList的实现如下:

public E remove(int index){

   RangeCheck(index);

   modCount++;

   E oldValue=(E) elementData[index];

  int numMoved=size-index-1;

  if(numMoved>0)

     System.arraycopy(elementData,index+1,elementData,index,numMoved);

     elementData[--size]=null;

     return oldValue;

}

可以看到,在ArrayList的每一次有效的元素删除操作后,都要进行数组的重组。并且删除的位置越靠前,数组重组时的开销越大。

public E remove(int index){

  return remove(entry(index));        

}

private Entry<E> entry(int index){

  if(index<0 || index>=size)

      throw new IndexOutBoundsException("Index:"+index+",size:"+size);

      Entry<E> e= header;

      if(index<(size>>1)){//要删除的元素位于前半段

         for(int i=0;i<=index;i++)

             e=e.next;

     }else{

         for(int i=size;i>index;i--)

             e=e.previous;

     }

         return e;

}

LinkedList的实现中,首先要通过循环找到要删除的元素。如果要删除的位置处于List的前半段,则从前往后找;若其位置处于后半段,则从后往前找。因此无论要删除较为靠前或者靠后的元素都是非常高效的;但要移除List中间的元素却几乎要遍历完半个List,在List拥有大量元素的情况下,效率很低。

4)容量参数

容量参数是ArrayListVector等基于数组的List的特有性能参数。它表示初始化的数组大小。当ArrayList所存储的元素数量超过其已有大小时。它便会进行扩容,数组的扩容会导致整个数组进行一次内存复制。因此合理的数组大小有助于减少数组扩容的次数,从而提高系统性能。

public  ArrayList(){

  this(10); 

}

public ArrayList (int initialCapacity){

   super();

   if(initialCapacity<0)

       throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity)

      this.elementData=new Object[initialCapacity];

}

ArrayList提供了一个可以制定初始数组大小的构造函数:

public ArrayList(int initialCapacity)

现以构造一个拥有100万元素的List为例,当使用默认初始化大小时,其消耗的相对时间为125ms左右,当直接制定数组大小为100万时,构造相同的ArrayList仅相对耗时16ms

5)遍历列表

遍历列表操作是最常用的列表操作之一,在JDK1.5之后,至少有3中常用的列表遍历方式:forEach操作,迭代器和for循环。

 

String tmp;

long start=System.currentTimeMills();    //ForEach

for(String s:list){

    tmp=s;

}

System.out.println("foreach spend:"+(System.currentTimeMills()-start));

start = System.currentTimeMills();

for(Iterator<String> it=list.iterator();it.hasNext();){   

   tmp=it.next();

}

System.out.println("Iterator spend;"+(System.currentTimeMills()-start));

start=System.currentTimeMills();

int size=;list.size();

for(int i=0;i<size;i++){                    

    tmp=list.get(i);

}

System.out.println("for spend;"+(System.currentTimeMills()-start));

构造一个拥有100万数据的ArrayList和等价的LinkedList,使用以上代码进行测试,测试结果的相对耗时如下表所示: https://i-blog.csdnimg.cn/blog_migrate/e1f8c8bb6880a0b706b165a186229561.jpeg

可以看到,最简便的ForEach循环并没有很好的性能表现,综合性能不如普通的迭代器,而是用for循环通过随机访问遍历列表时,ArrayList表项很好,但是LinkedList的表现却无法让人接受,甚至没有办法等待程序的结束。这是因为对LinkedList进行随机访问时,总会进行一次列表的遍历操作。性能非常差,应避免使用。

 

ArrayList和LinkedList的区别以及优缺点

 

ArrayList和LinkedList都是实现了List接口的容器类,用于存储一系列的对象引用。他们都可以对元素的增删改查进行操作。

 

对于ArrayList,它在集合的末尾删除或添加元素所用的时间是一致的,但是在列表中间的部分添加或删除时所用时间就会大大增加。但是它在根据索引查找元素的时候速度很快。

 

对于LinkedList则相反,它在插入、删除集合中任何位置的元素所花费的时间都是一样的,但是它根据索引查询一个元素的时候却比较慢。

 

ArrayList和LinkedList的大致区别:

 

1.ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。

2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。

3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

1

2

3

他们在性能上的有缺点:

 

1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。

2.在ArrayList集合中添加或者删除一个元素时,当前的列表所所有的元素都会被移动。而LinkedList集合中添加或者删除一个元素的开销是固定的。

3.LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为。

4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

1

2

3

4

所以在我们进行对元素的增删查操作的时候,进行 查操作时用ArrayList,进行增删操作的时候最好用LinkedList。

---------------------

作者:子弹与信仰

来源:CSDN

原文:https://blog.csdn.net/qq_32679815/article/details/78907437

版权声明:本文为博主原创文章,转载请附上博文链接!

 

ArraylistLinkedlist遍历方式性能分析

本文主要介绍ArrayListLinkedList这两种list的常用循环遍历方式,各种方式的性能分析。熟悉java的知道,常用的list的遍历方式有以下几种:

1for-each

1

2

3

4

5

List<String> testList = new ArrayList<String>();

for (String tmp : testList)

{  

  //use tmp;

}

这种遍历方式是最常用的遍历方式,因为书写比较方便,而且不需要考虑数组越界的问题,Effective-Java中推荐使用此种写法遍历。

2迭代器方式

1

2

3

4

5

List<String> testList = new ArrayList<String>();

for (Iterator<String> iterator = testList.iterator(); iterator.hasNext();)

{

    //String tmp = iterator.next();

}

3下标递增或递减循环

1

2

3

4

5

List<String> testList = new ArrayList<String>();

for (int i = 0; i < testList.size(); i++;)

{

    //String tmp = testList.get(i);

}

下标递增或者递减循环是最早接触到的遍历方式,会经常出现数组越界的问题。

以上三种遍历方式是在使用list时最常用的方式,那么这三种方式在遍历的速度已经性能上又有什么区别呢?我们从数据的底层实现上来进行分析。

List底层储存都是使用数组来进行存储的,ArrayList是直接通过数组来进行存储,而LinkedList则是使用数组模拟指针,来实现链表的方式,因此从这里就可以总结出,ArrayList在使用下标的方式循环遍历的时候性能最好,通过下标可以直接取数据,速度最快。而LinkedList因为有一层指针,无法直接取到对应的下标,因此在使用下标遍历时就需要计算对应的下面是哪个元素,从指针的头一步一步的走,所以效率就很低。想到指针就会联想到迭代器,迭代器可以指向下一个元素,而迭代器就是使用指针来实现的,因此LinkedList在使用迭代器遍历时会效率最高,迭代器直接通过LinkedList的指针进行遍历,ArrayList在使用迭代器时,因为要通过ArrayList先生成指针,因此效率就会低于下标方式,而for-each又是在迭代器基础上又进行了封装,因此效率会更低一点,但是会很接近迭代器。

总结:在进行list遍历时,如果是对ArrayList进行遍历,推荐使用下标方式,如果是LinkedList则推荐使用迭代器方式。

LinkedList通常比ArrayList快原因

2018年05月03日 19:03:47 simon_it 阅读数:325

  实验  

首先我们做一个实验:将10万条String类型的数据分别添加到一个LinkedList和一个ArrayList中,且每次都是在第0位(即首位)插入数据,代码如下

结果是LinkedList比ArrayList要快: ArrayList平均用了500毫秒,而LinkedList平均只用了15毫秒;进行多次实验你就会发现,添加的数据量越大,LinkedList的速度优势越明显,这是为什么呢?

[java] view plain copy

  1. import org.junit.Test;  
  2. import java.util.ArrayList;  
  3. import java.util.LinkedList;  
  4. import java.util.List;  
  5.   
  6. public class ListAddTest {  
  7.   
  8.     List<String> arrList = new ArrayList<String>();  
  9.     List<String> lnkList = new LinkedList<String>();  
  10.   
  11.     void add(List<String> list) {  
  12.   
  13.         long startTime = System.currentTimeMillis();  
  14.         System.out.println(”开始的时间:” + startTime);  
  15.         for (int i = 0; i < 100000; i++) {  
  16.             list.add(0, String.valueOf(i));  
  17.         }  
  18.         long endTime = System.currentTimeMillis();  
  19.         System.out.println(”结束的时间:” + endTime);  
  20.         System.out.println(”总耗时:” + (endTime - startTime));  
  21.   
  22.     }  
  23.   
  24.     @Test  
  25.     public void addTimeTest() {  
  26.   
  27.         add(arrList);  
  28.         // 开始的时间:1487783199226  
  29.         // 结束的时间:1487783199741  
  30.         // 总耗时:515  
  31.   
  32.         add(lnkList);  
  33.         // 开始的时间:1487783199741  
  34.         // 结束的时间:1487783199756  
  35.         // 总耗时:15  
  36.   
  37.     }  
  38.   
  39. }  
import org.junit.Test;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
 
public class ListAddTest {
 
    List<String> arrList = new ArrayList<String>();
    List<String> lnkList = new LinkedList<String>();
 
    void add(List<String> list) {
 
        long startTime = System.currentTimeMillis();
        System.out.println("开始的时间:" + startTime);
        for (int i = 0; i < 100000; i++) {
            list.add(0, String.valueOf(i));
        }
        long endTime = System.currentTimeMillis();
        System.out.println("结束的时间:" + endTime);
        System.out.println("总耗时:" + (endTime - startTime));
 
    }
 
    @Test
    public void addTimeTest() {
 
        add(arrList);
        // 开始的时间:1487783199226
        // 结束的时间:1487783199741
        // 总耗时:515
 
        add(lnkList);
        // 开始的时间:1487783199741
        // 结束的时间:1487783199756
        // 总耗时:15
 
    }
 
}

 

  分析  

首先,阅读JDK的文档,我们从中可以知道,ArrayList实际上是一个可变长的数组,LinkedList则是由相互引用的节点组成的双向链表

紧接着我们就要知道,在增加数据时LinkedList和ArrayList分别在底层发生了什么?于是略读JDK源码我们就可以得出:
 


 

● 既然LinkedList是一个由相互引用的节点组成的双向链表,那么当把数据插入至该链表某个位置时,该数据就会被组装成一个新的节点,随后只需改变链表中对应的两个节点之间的引用关系,使它们指向新节点,即可完成插入(如下图);同样的道理,删除数据时,只需删除对应节点的引用即可


 


 

● 而ArrayList是一个可变长数组,插入数据时,则需要先将原始数组中的数据复制到一个新的数组,随后再将数据赋值到新数组的指定位置(如下图);删除数据时,也是将原始数组中要保留的数据复制到一个新的数组


 


 

  结论  

因此,在添加或删除数据的时候,ArrayList经常需要复制数据到新的数组,而LinkedList只需改变节点之间的引用关系,这就是LinkedList在添加和删除数据的时候通常比ArrayList要快的原因

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值