ArrayList和LinkedList的區別?


從圖中能夠看出,ArrayListLinkedList都是List接口的實現類,所以都實現了List的全部未實現的方法,只是實現的方式有所不一樣,(從中能夠看出面向接口的好處, 對於不一樣的需求就有不一樣的實現!),而List接口繼承了Collection接口,Collection接口又繼承了Iterable接口,所以能夠看出List同時擁有了Collection與Iterable接口的特性.java

ArrayList

ArrayList是List接口的可變數組的實現。實現了全部可選列表操做,並容許包括 null 在內的全部元素。除了實現 List 接口外,此類還提供一些方法來操做內部用來存儲列表的數組的大小。
每一個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它老是至少等於列表的大小。隨着向ArrayList中不斷添加元素,其容量也自動增加。自動增加會帶來數據向新數組的從新拷貝,所以,若是可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前,應用程序也可使用ensureCapacity操做來增長ArrayList實例的容量,這能夠減小遞增式再分配的數量。
注意,此實現不是同步的。若是多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。這一般是經過同步那些用來封裝列表的對象來實現的。但若是沒有這樣的對象存在,則該列表須要運用{@link Collections#synchronizedList Collections.synchronizedList}來進行「包裝」,該方法最好是在建立列表對象時完成,爲了不對列表進行突發的非同步操做。數組

List list = Collections.synchronizedList(new ArrayList(...));複製代碼

建議在單線程中才使用ArrayList,而在多線程中能夠選擇Vector或者CopyOnWriteArrayList。bash

擴容

數組有個明顯的特色就是它的容量是固定不變的,一旦數組被建立則容量則沒法改變。因此在往數組中添加指定元素前,首先要考慮的就是其容量是否飽和。數據結構

若接下來的添加操做會時數組中的元素超過其容量,則必須對其進行擴容操做。受限於數組容量固定不變的特性,擴容的本質其實就是建立一個容量更大的新數組,再將舊數組的元素複製到新數組當中去。多線程

這裏以 ArrayList 的 添加操做爲例,來看下 ArrayList 內部數組擴容的過程。函數

public boolean add(E e) {
	// 關鍵 -> 添加以前,校驗容量
	ensureCapacityInternal(size + 1); 
	
	// 修改 size,並在數組末尾添加指定元素
	elementData[size++] = e;
	return true;
}複製代碼

能夠發現 ArrayList 在進行添加操做前,會檢驗內部數組容量並選擇性地進行數組擴容。在 ArrayList 中,經過私有方法 ensureCapacityInternal 來進行數組的擴容操做。下面來看具體的實現過程:post

  • 擴容操做的第一步會去判斷當前 ArrayList 內部數組是否爲空,爲空則將最小容量 minCapacity 設置爲 10。
// 內部數組的默認容量
private static final int DEFAULT_CAPACITY = 10;

// 空的內部數組
private static final Object[] EMPTY_ELEMENTDATA = {};

// 關鍵 -> minCapacity = seize+1,即表示執行完添加操做後,數組中的元素個數 
private void ensureCapacityInternal(int minCapacity) {
	// 判斷內部數組是否爲空
	if (elementData == EMPTY_ELEMENTDATA) {
		// 設置數組最小容量(>=10)
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	ensureExplicitCapacity(minCapacity);
}複製代碼
  • 接着判斷添加操做會不會致使內部數組的容量飽和。
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;
	
	// 判斷結果爲 true,則表示接下來的添加操做會致使元素數量超出數組容量
	if (minCapacity - elementData.length > 0){
		// 真正的擴容操做
		grow(minCapacity);
	}
}複製代碼
  • 數組容量不足,則進行擴容操做,關鍵的地方有兩個:擴容公式、經過從舊數組複製元素到新數組完成擴容操做。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
	
	int oldCapacity = elementData.length;
	
	// 關鍵-> 容量擴充公式
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	
	// 針對新容量的一系列判斷
	if (newCapacity - minCapacity < 0){
		newCapacity = minCapacity;
	}
	if (newCapacity - MAX_ARRAY_SIZE > 0){
		newCapacity = hugeCapacity(minCapacity);
	}
		
	// 關鍵 -> 複製舊數組元素到新數組中去
	elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
	if (minCapacity < 0){
		throw new OutOfMemoryError();
	}
			
	return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}複製代碼

關於 ArrayList 擴容操做,整個過程以下圖:gradle


LinkedList

  1. LinkedList 是一個繼承於AbstractSequentialList的雙向鏈表。它也能夠被看成堆棧、隊列或雙端隊列進行操做。
  2. LinkedList 實現 List 接口,能對它進行隊列操做。
  3. LinkedList 實現 Deque 接口,即能將LinkedList看成雙端隊列使用。
  4. LinkedList 實現了Cloneable接口,即覆蓋了函數clone(),能克隆。
  5. LinkedList 實現java.io.Serializable接口,這意味着LinkedList支持序列化,能經過序列化去傳輸。
  6. LinkedList 是非同步的。

如上圖所示,LinkedList底層使用的雙向鏈表結構,有一個頭結點和一個尾結點,雙向鏈表意味着咱們能夠從頭開始正向遍歷,或者是從尾開始逆向遍歷,而且能夠針對頭部和尾部進行相應的操做。this

private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;spa

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

總結

1.ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
2.對於隨機訪問get和set,ArrayList以爲優於LinkedList,由於LinkedList要移動指針。
3.對於新增和刪除操做add和remove,LinedList比較佔優點,由於ArrayList要移動數據。

相關文章
相關標籤/搜索