ArrayList和LinkedList的區別?

AskHarries發表於2019-03-03


從圖中可以看出,ArrayListLinkedList都是List介面的實現類,因此都實現了List的所有未實現的方法,只是實現的方式有所不同,(從中可以看出面向介面的好處, 對於不同的需求就有不同的實現!),而List介面繼承了Collection介面,Collection介面又繼承了Iterable介面,因此可以看出List同時擁有了Collection與Iterable介面的特性.

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。

擴容

陣列有個明顯的特點就是它的容量是固定不變的,一旦陣列被建立則容量則無法改變。所以在往陣列中新增指定元素前,首先要考慮的就是其容量是否飽和。

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

這裡以 ArrayList 的 新增操作為例,來看下 ArrayList 內部陣列擴容的過程。

public boolean add(E e) {
	// 關鍵 -> 新增之前,校驗容量
	ensureCapacityInternal(size + 1); 
	
	// 修改 size,並在陣列末尾新增指定元素
	elementData[size++] = e;
	return true;
}複製程式碼

可以發現 ArrayList 在進行新增操作前,會檢驗內部陣列容量並選擇性地進行陣列擴容。在 ArrayList 中,通過私有方法 ensureCapacityInternal 來進行陣列的擴容操作。下面來看具體的實現過程:

  • 擴容操作的第一步會去判斷當前 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 擴容操作,整個過程如下圖:

ArrayList和LinkedList的區別?

LinkedList

  1. LinkedList 是一個繼承於AbstractSequentialList的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。
  2. LinkedList 實現 List 介面,能對它進行佇列操作。
  3. LinkedList 實現 Deque 介面,即能將LinkedList當作雙端佇列使用。
  4. LinkedList 實現了Cloneable介面,即覆蓋了函式clone(),能克隆。
  5. LinkedList 實現java.io.Serializable介面,這意味著LinkedList支援序列化,能通過序列化去傳輸。
  6. LinkedList 是非同步的。

如上圖所示,LinkedList底層使用的雙向連結串列結構,有一個頭結點和一個尾結點,雙向連結串列意味著我們可以從頭開始正向遍歷,或者是從尾開始逆向遍歷,並且可以針對頭部和尾部進行相應的操作。

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

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要移動資料。

ArrayList和LinkedList的區別?



相關文章