Java:ArrayList和LinkedList的区别及相关测试

  Java中ArrayList和LinkedList分别对应着两种线性表的实现方式:顺序存储和链式存储。
  ArrayList基于动态数组的数据结构,LinkedList为循环双向链表数据结构。
  以下,将以100万元素的List为例,比较ArrayList和LinkedList的区别:

1.增加元素到列表尾端

  测试代码:

private long start;
private long time;
private long arraytime = 0;
private long linkedtime = 0;

public void addList(){

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

        //累加ArrayList添加时间
        start = System.currentTimeMillis();
        array.add(i);
        time = (System.currentTimeMillis() - start);
        arraytime = arraytime + time;

        //累加LinkedList添加时间
        start = System.currentTimeMillis();
        linked.add(i);
        time = (System.currentTimeMillis() - start);
        linkedtime = linkedtime + time;
    }

    System.out.println("add\t\t\t spend:\t array->" + arraytime + "\t linked->" + linkedtime);
}

  运行结果:

add          spend:  array->32   linked->157

  结果说明:
  ArrayList
  ArrayList中add()方法的性能决定于ensureCapacity()方法。相关代码实现如下:

public boolean add(E e){

    ensureCapacity(size+1); //确保内部数组有足够的空间
    elementData[size++] = e; //将元素加入到数组的末尾,完成添加

    return true;      
} 
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
  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.于列表任意位置插入、删除、修改元素

  增加

public void addByIndex(){

    int index = (int)(Math.random() * 1000000);

    start = System.currentTimeMillis();
    array.add(index, -1);
    arraytime = (System.currentTimeMillis() - start);

    start = System.currentTimeMillis();
    linked.add(index, -1);
    linkedtime = (System.currentTimeMillis() - start);

    System.out.println("addByIndex\t\t spend:\t array->" + arraytime + "\t linked->" + linkedtime + "\t index->" + index);
}

  运行10次的结果:

addByIndex       spend:  array->1    linked->0   index->21828
addByIndex       spend:  array->1    linked->0   index->38559
addByIndex       spend:  array->2    linked->0   index->57708
addByIndex       spend:  array->0    linked->1   index->158109
addByIndex       spend:  array->1    linked->2   index->276408
addByIndex       spend:  array->1    linked->3   index->283075
addByIndex       spend:  array->0    linked->3   index->416828
addByIndex       spend:  array->1    linked->2   index->570367
addByIndex       spend:  array->0    linked->1   index->809131
addByIndex       spend:  array->0    linked->1   index->895882

  经多次运行以上代码,发现当index较小,即拆入元素位置靠前时,LinkedList插入效率高于ArrayList,其余时间ArrayList插入效率均高于LinkedList。
  结果说明:
  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中的位置越是靠前,数组重组的开销也越大。
  相比于ArrayList,LinkeList很好的解决了这个缺点:

public void add(int index,E element){
    addBefore(element,(index==size?header:entry(index)));
}

  但是,LinkeList要插入元素时,首先要找到需要插入的位置,若该位置位于List前半段,则从前往后找;反之,从后往前找。因此无论要插入较为靠前或者靠后的元素都是非常高效的;但要在List中间插入元素却几乎要遍历完半个List,在List拥有大量元素的情况下,效率很低。
  删除与修改测试代码、结果、原理与插入类似:

addByIndex       spend:  array->1    linked->4   index->607482
removeByIndex    spend:  array->0    linked->4   index->607482
setByIndex       spend:  array->0    linked->3   index->607482
3.遍历列表

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

//foreach
public void traverseByForeach(){

    Integer tmp1 = null;
    start = System.currentTimeMillis();
    for(Integer i : array){
        tmp1 = i;
    }
    arraytime = (System.currentTimeMillis() - start);

    Integer tmp2 = null;
    start = System.currentTimeMillis();
    for(Integer i : linked){
        tmp2 = i;
    }
    linkedtime = (System.currentTimeMillis() - start);

    System.out.println("traverseByforeach\t spend:\t array->" + arraytime + "\t linked->" + linkedtime + "\t last->array " + tmp1 + "\t linked " + tmp2);
}

//迭代器
public void traverseByIterator(){

    Integer tmp1 = null;
    start = System.currentTimeMillis();
    for(Iterator<Integer> it = array.iterator();it.hasNext();){
        tmp1 = it.next();
    }
    arraytime = (System.currentTimeMillis() - start);

    Integer tmp2 = null;
    start = System.currentTimeMillis();
    for(Iterator<Integer> it = linked.iterator();it.hasNext();){
        tmp2 = it.next();
    }
    linkedtime = (System.currentTimeMillis() - start);

    System.out.println("traverseByIterator\t spend:\t array->" + arraytime + "\t linked->" + linkedtime + "\t last->array " + tmp1 + "\t linked " + tmp2);
}

//for循环
public void traverseByForLoop(){

    Integer tmp1 = null;
    start = System.currentTimeMillis();
    for(int i=0;i<array.size();i++){                     
        tmp1 = array.get(i);
    }
    arraytime = (System.currentTimeMillis() - start);

    Integer tmp2 = null;
    start = System.currentTimeMillis();
    for(int i=0;i<linked.size();i++){                     
        tmp2 = linked.get(i);
    }
    linkedtime = (System.currentTimeMillis() - start);

    System.out.println("traverseByForLoop\t spend:\t array->" + arraytime + "\t linked->" + linkedtime + "\t last->array " + tmp1 + "\t linked " + tmp2);
}

  运行结果:

traverseByforeach    spend:  array->19   linked->12     last->array 999999   linked 999999
traverseByIterator   spend:  array->13   linked->11     last->array 999999   linked 999999
traverseByForLoop    spend:  array->12   linked->874417 last->array 999999   linked 999999

  结果说明:
  可以看到,最简便的ForEach循环并没有很好的性能表现,综合性能不如普通的迭代器,而是用for循环通过随机访问遍历列表时,ArrayList性能很好,但是LinkedList的结果却很夸张。这是因为对LinkedList进行随机访问时,总会进行一次列表的遍历操作。性能非常差,应避免使用。

  注:以上,仅为博主以100万数字元素的情况下的测试结果,在实际应用中,我们应该结合不同的应用环境,灵活选取最合适的数据结构。
  容量参数
  容量参数是ArrayList和Vector等基于数组的List的特有性能参数。它表示初始化的数组大小。当ArrayList所存储的元素数量超过其已有大小时。它便会进行扩容,数组的扩容会导致整个数组进行一次内存复制。因此合理的数组大小有助于减少数组扩容的次数,从而提高系统性能。
  ArrayList提供了一个可以制定初始数组大小的构造函数:

public ArrayList(int initialCapacity)

  其实现代码为:

public  ArrayList(){
    this(10);  
}
public ArrayList (int initialCapacity){
    super();
    if(initialCapacity<0){
        throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity)
    }
    this.elementData=new Object[initialCapacity];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值