java集合类(四)ArrayList与LinkedList比较

时间:2021-09-11 16:59:13

概述

  ArrayList与LinkedList均实现了List接口,所以从用户使用的角度来看是区别不大的。但是由于其底层实现的不同,对用户来讲无差异的操作(如:get,add,remove)底层所做的事情完全不一样,从而使得他们有着各自的应用场景。

ArrayList与LinkedList类的声明

  • 1 ArrayList
public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable {

//具体代码省略
}
  • 2 LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable{

//具体代码省略
}

  从上面两个类的声明可以看到,他们均实现了List,Cloneable, Serializable 接口,他们都具有List接口规定的行为操作。AbstractListAbstractSequentialList两个抽象类是对List接口的简化,这里不做详细探讨,此外我们可以发现ArrayList实现了RandomAccess接口,而LinkedList却没有实现此接口。RandomAccess到底有什么用呢?我们后续会讲到。

底层存储与存取性能

  • 1.我们知道ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构。
  • 2.就是由于底层存储的不同导致对于随机访问getsetArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  • 3.而对于新增和删除操作addremoveLinedList比较占优势,因为ArrayList要移动数据。
  • 4.当容量不足时,ArrayList需要进行扩容操作,实际上就是创建出一个更大的数组,然后将旧数组中的值拷贝到新数组中。
      以上几点导致他们两者之间各有所长,所以我们在使用时要根据具体场景作出正确的选择,我们简单总结如下:
  • 1) 如果在使用的过程中需要频繁的做插入、删除元素的操作应该使用LinkedList
  • 2) 如果在使用的过程中需要快速随机访问元素,而插入删除操作较少,我们应该使用ArrayList

      上面我们提到了RandomAccess接口,这个接口有什么用呢?我们来看下它的声明:
      

package java.util;

/**
* Marker interface used by <tt>List</tt> implementations to indicate that
* they support fast (generally constant time) random access. The primary
* purpose of this interface is to allow generic algorithms to alter their
* behavior to provide good performance when applied to either random or
* sequential access lists.
*
* <p>The best algorithms for manipulating random access lists (such as
* <tt>ArrayList</tt>) can produce quadratic behavior when applied to
* sequential access lists (such as <tt>LinkedList</tt>). Generic list
* algorithms are encouraged to check whether the given list is an
* <tt>instanceof</tt> this interface before applying an algorithm that would
* provide poor performance if it were applied to a sequential access list,
* and to alter their behavior if necessary to guarantee acceptable
* performance.
*
* <p>It is recognized that the distinction between random and sequential
* access is often fuzzy. For example, some <tt>List</tt> implementations
* provide asymptotically linear access times if they get huge, but constant
* access times in practice. Such a <tt>List</tt> implementation
* should generally implement this interface. As a rule of thumb, a
* <tt>List</tt> implementation should implement this interface if,
* for typical instances of the class, this loop:
* <pre>
* for (int i=0, n=list.size(); i &lt; n; i++)
* list.get(i);
* </pre>
* runs faster than this loop:
* <pre>
* for (Iterator i=list.iterator(); i.hasNext(); )
* i.next();
* </pre>
*
* @since 1.4
*/
public interface RandomAccess {
}

我们可以看到它是一个空接口,那么它的作用是什么呢?其实它仅仅起到了标识的作用。我们可以看上面的说明第一句话:

 Marker interface used by <tt>List</tt> implementations to indicate that they support fast (generally constant time) random access.The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.

什么意思呢?其实就是:

用于标识List的实现类,表明其可以进行快速随机访问。主要目的是能够在使用的时候为了提供好的性能(随机访问或顺序访问)而更改他们的行为。

说起来有点拗口,其实就是我们在使用的过程中,可以用instanceof来判断是否实现了RandomAccess 接口,从而选择不同的方式进行随机访问或者顺序访问。举个例子:
  
  假如有一个100000000长度的List(我们不知道它的具体类型ArrayList or LinkedList or其他),我们要遍历里面的值,并且效率达到最佳,我们该如何进行遍历?看实现代码:

        if (list instanceof RandomAccess) {
for (int m = 0; m < list.size(); m++) {
System.out.println(list.get(m));
}
} else {
Iterator iter = list.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}

通过上面的代码我们可以使得遍历操作的性能最好。因为假如一个List实现了RandomAccess,那么它必定支持快速随机访问(例如ArrayList),通过get()、set()就可以达到非常好的性能,反之,我们通过Iterator 来遍历是最佳选择。相同的应用在java.util.Collections中就有体现,随便举其中一个例子:

    public static <T> void fill(List<? super T> list, T obj) {
int size = list.size();
if (size < FILL_THRESHOLD || list instanceof RandomAccess) {
for (int i=0; i<size; i++)
list.set(i, obj);
} else {
ListIterator<? super T> itr = list.listIterator();
for (int i=0; i<size; i++) {
itr.next();
itr.set(obj);
}
}
}

这个方法的作用是将list中的每个值全部填充为obj,也就相当于遍历操作。这里的FILL_THRESHOLD 等于25,意思就是当list的大小小于25或者list实现了RandomAccess接口,就使用:for (int i=0; i<size; i++) 这种方式,否则使用:Iterator