爲方便開發人員,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
從這三個類定義就能夠看出一些信息:算法
下面詳細說說這三個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莫屬。