說說JDK中的List-ArrayList、Vector、LinkedList

爲方便開發人員,JDK提供了一套主要數據結構的實現,好比List、Map等。今兒說說List接口。前端

List接口的一些列實現中,最經常使用最重要的就是這三個:ArrayList、Vector、LinkedList。java

JDK中這三個類的定義:node

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

從這三個類定義就能夠看出一些信息:算法

  • 三個都直接實現了AbstractList這個抽象類
  • ArrayList和Vector都實現了RandomAccess接口,而LinkedList沒有,這是什麼意思呢?在JDK中,RandomAccess接口是一個空接口,因此它沒有實際意義,就是一個標記,標記這個類支持快速隨機訪問,因此,arrayList和vector是支持隨機訪問的,可是LinkedList不支持
  • serializbale接口表名,他們都支持序列化

下面詳細說說這三個List實現。數組

這三個裏面,ArrayList和Vector使用了數組的實現,至關於封裝了對數組的操做。這也正是他們可以支持快速隨機訪問的緣由,多說一句,JDK中全部基於數組實現的數據結構都可以支持快速隨機訪問。安全

ArrayList和Vector的實現上幾乎都使用了相同的算法,他們的主要區別就是ArrayList沒有對任何一個方法作同步,因此不是線程安全的;而Vector中大部分方法都作了線程同步,是線程安全的。數據結構

LinkedList使用的是雙向循環鏈表的數據結構。因爲是基於鏈表的,因此是無法實現隨機訪問的,只能順序訪問,這也正式它沒有實現RandomAccess接口的緣由。dom

正式因爲ArrayList、Vector和LinkedList所採用的數據結構不一樣,註定他們適用的是徹底不一樣的場景。性能

經過閱讀這幾個類的源碼,咱們能夠看到他們實現的不一樣。ArrayList和Vector基本同樣,咱們就拿ArrayList和LinkedList作對比。測試

在末尾增長一個元素

ArrayList中的add方法實現以下:

1     public boolean add(E e) {
2         ensureCapacityInternal(size + 1);  // Increments modCount!!
3         elementData[size++] = e;
4         return true;
5     }

這個方法作兩件事情,首先確保數組空間足夠大,而後在數組末尾增長元素而且經過後++使得完成size+1。

從這個代碼能夠看出,若是數組空間足夠大,那麼只是數組的add操做就是O(1)的性能,很是高效。

在看看ensureCapacityInternal這個方法的實現:

 1     private void ensureCapacityInternal(int minCapacity) {
 2         modCount++;
 3         // overflow-conscious code
 4         if (minCapacity - elementData.length > 0)
 5             grow(minCapacity);
 6     }
 7 
 8     private void grow(int minCapacity) {
 9         // overflow-conscious code
10         int oldCapacity = elementData.length;
11         int newCapacity = oldCapacity + (oldCapacity >> 1);
12         if (newCapacity - minCapacity < 0)
13             newCapacity = minCapacity;
14         if (newCapacity - MAX_ARRAY_SIZE > 0)
15             newCapacity = hugeCapacity(minCapacity);
16         // minCapacity is usually close to size, so this is a win:
17         elementData = Arrays.copyOf(elementData, newCapacity);
18     }

能夠看出,若是數組空間不夠,那麼這個方法就會作數組擴容和數組複製操做,看第11行,JDK利用移位運算符進行擴容計算,>>1右移一位表示除2,因此newCapacity就是擴容爲原來的1.5倍。

PS:這裏的代碼都是JDK1.7中的實現,JDK1.7對1.6的不少代碼作了優化,好比上面這段擴容代碼,在JDK1.6中第11行是直接除2,顯然,移位運算要更高效。

在看看LinkedList中的add方法:

 1     public boolean add(E e) {
 2         linkLast(e);
 3         return true;
 4     }
 5 
 6     void linkLast(E e) {
 7         final Node<E> l = last;
 8         final Node<E> newNode = new Node<>(l, e, null);
 9         last = newNode;
10         if (l == null)
11             first = newNode;
12         else
13             l.next = newNode;
14         size++;
15         modCount++;
16     }
1         Node(Node<E> prev, E element, Node<E> next) {
2             this.item = element;
3             this.next = next;
4             this.prev = prev;
5         }

從這段add代碼能夠看出,LinkedList因爲使用了鏈表,因此不須要進行擴容,直接把元素加到鏈表最後,把新元素的前驅指向以前的last元素,並把last元素指向新元素就ok。這也是一個O(1)的性能。

測試一下:

 1     public static void main(String[] args) {
 2         // TODO Auto-generated method stub
 3         long begin = System.currentTimeMillis();
 4         
 5 //        List<Object> list = new ArrayList<Object>();
 6         List<Object> list = new LinkedList<Object>();
 7         Object obj = new Object();
 8         for(int i=0; i<50000; i++){
 9             list.add(obj);
10         }
11         
12         long end = System.currentTimeMillis();
13         long time = end - begin;
14         System.out.println(time+"");
15 
16     }

分別對ArrayList和LinkedList作末尾add操做,循環50000次,ArrayList耗時6ms,而LinkedList耗時8ms,這是因爲LinkedList在add時候須要更多的對象建立和賦值操做。

在任意位置插入元素

ArrayList中的實現以下:

1     public void add(int index, E element) {
2         rangeCheckForAdd(index);
3 
4         ensureCapacityInternal(size + 1);  // Increments modCount!!
5         System.arraycopy(elementData, index, elementData, index + 1,
6                          size - index);
7         elementData[index] = element;
8         size++;
9     }

這段代碼,首先先檢查數組容量,容量不夠先擴容,而後把index以後的數組日後挪一個,最後在index位置放上新元素。因爲數組是一塊連續內存空間,因此在任意位置插入,都會致使這個其後數組後挪一位的狀況,須要作一次數組複製操做,很明顯,若是有大量的隨機插入,那麼這個數組複製操做開銷會很大,並且插入的越靠前,數組複製開銷越大。

LinkedList中的實現:

 1     public void add(int index, E element) {
 2         checkPositionIndex(index);
 3 
 4         if (index == size)
 5             linkLast(element);
 6         else
 7             linkBefore(element, node(index));
 8     }
 9 
10     void linkBefore(E e, Node<E> succ) {
11         // assert succ != null;
12         final Node<E> pred = succ.prev;
13         final Node<E> newNode = new Node<>(pred, e, succ);
14         succ.prev = newNode;
15         if (pred == null)
16             first = newNode;
17         else
18             pred.next = newNode;
19         size++;
20         modCount++;
21     }

這段代碼,取到原先index處節點的前驅,變成新節點的前驅 ,同時把原先index變成新節點的後驅,這樣就完成了新節點的插入。這個就是鏈表的優點,不存在數據複製操做,性能和在最後插入是同樣的。

測試一種極端狀況,每次都在最前端插入元素:

 1     public static void main(String[] args) {
 2         // TODO Auto-generated method stub
 3         long begin = System.currentTimeMillis();
 4         
 5 //        List<Object> list = new ArrayList<Object>();
 6         List<Object> list = new LinkedList<Object>();
 7         Object obj = new Object();
 8         for(int i=0; i<50000; i++){
 9             list.add(0,obj);
10         }
11         
12         long end = System.currentTimeMillis();
13         long time = end - begin;
14         System.out.println(time+"");
15 
16     }

測試結果是:ArrayList耗時1400ms,而LinkedList只耗時12ms。能夠看出,在隨機插入的時候,二者的性能差別就很明顯了。

小結一下,從上面的源碼剖析和測試結果能夠看出這三種List實現的一些典型適用場景,若是常常對數組作隨機插入操做,特別是插入的比較靠前,那麼LinkedList的性能優點就很是明顯,而若是都只是末尾插入,則ArrayList更佔據優點,若是須要線程安全,則非Vector莫屬。

相關文章
相關標籤/搜索