從圖中可以看出,ArrayList與LinkedList都是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 擴容操作,整個過程如下圖:
LinkedList
- LinkedList 是一個繼承於AbstractSequentialList的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。
- LinkedList 實現 List 介面,能對它進行佇列操作。
- LinkedList 實現 Deque 介面,即能將LinkedList當作雙端佇列使用。
- LinkedList 實現了Cloneable介面,即覆蓋了函式clone(),能克隆。
- LinkedList 實現java.io.Serializable介面,這意味著LinkedList支援序列化,能通過序列化去傳輸。
- 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要移動資料。