Java 中的 List 是非常常用的資料型別。List 是有序的 Collection,Java List 一共有三個實現類,分別是:ArrayList、Vector、LinkedList
本文分析基於 JDK8
ArrayList
ArrayList 繼承自 AbstractList,實現了 List 介面。底層基於陣列實現容量大小動態變化,初始容量為 10,允許值為 null,有序,非執行緒安全,擅長隨機訪問
ArrayList 還實現了 RandomAccess、Cloneable、Serializable 介面,所以 ArrayList 是支援快速訪問、複製、序列化的
-
RandomAccess
標記介面,用來表明其支援快速隨機訪問。如果是實現了這個介面的 List,那麼使用 for 迴圈的方式獲取資料會優於用迭代器獲取資料
-
Serializable
標記該類支援序列化
-
Cloneable
允許在堆中克隆出一塊和原物件一樣的物件,並將這個物件的地址賦予新的引用。ArrayList 提供的是一種深克隆機制,即克隆除自身物件以外的所有物件,包括自身所包含的所有物件例項。實現方式是先呼叫 super.clone() 方法克隆出一個新物件,然後再手動將原陣列中的值複製到一個新的陣列,並賦值
ArrayList 擴容機制
擴容機制應該是面試中最常問的了。其他關於 ArrayList 的一些瑣碎方法我就不細說了,主要介紹一下擴容機制。首先了解一下 ArrayList 的成員屬性
// 表示 ArrayList 的預設容量大小
private static final int DEFAULT_CAPACITY = 10;
// 一個空的 Object 陣列物件,長度為 0,如果使用預設建構函式建立,則 elementData 預設是該值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// ArrayList 中存放的實際元素個數
private int size;
// 當前元素物件存放的陣列,不參與序列化
transient Object[] elementData;
執行 add 方法時,會先執行 ensureCapacityInternal 方法,判斷當前陣列容量是否足夠,不夠就擴容。然後將待新增元素加到 elementData 末尾
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// minCapacity > elementData.length 則擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
再分析一下 ensureCapacityInternal 方法,此時 minCapacity 是 size + 1,這裡有兩個巢狀方法 calculateCapacity 和 ensureExplicitCapacity,作用分別如下:
-
calculateCapacity
如果當前陣列為空,則先設定容量為預設值 10,此時還未初始化陣列
-
ensureExplicitCapacity
確認實際的容量,如果不夠就擴容,關鍵的擴容函式 grow 就在這裡
擴充套件陣列大小,首先將容量擴大為原來的 1.5 倍,如果陣列是空陣列,則將陣列初始化,預設容量為 10,如果不是,再判斷是否超出最大容量,超過直接賦予最大值,否則賦予新值,複製原陣列到新陣列
private void grow(int minCapacity) {
// 擴容前的容量
int oldCapacity = elementData.length;
// oldCapacity 右移一位,等於除以二
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);
}
LinkedList
LinkedList 繼承自 AbstractSequentialList,實現了 List 和 Deque 介面,基於雙向連結串列實現,每個節點都包含了對前一個和後一個元素的引用,可以被當作堆疊、佇列或雙端佇列進行操作,有序,非執行緒安全
// 指向連結串列的第一個節點
transient Node<E> first;
// 指向連結串列的最後一個節點
transient Node<E> last;
JDK8 中 LinkedList 有一個靜態內部類 Node,它包括的屬性有:當前節點所包含的值,上一個節點,下一個節點
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;
}
}
除此之外就沒啥好分析的了,LinkedList 不存在容量不足的問題,克隆函式也是將所有元素全都克隆到新的 LinkedList 物件
Vector
Vector 是一個向量佇列,和 ArrayList 類似,繼承自 AbstractList,實現了 List 介面,就連額外介面也是一樣。不同之處在於:
- Vector 使用 synchronized 保證執行緒同步
- Vector 中遺留了大量傳統的方法,這些方法不屬於集合框架
Vector 有四個構造方法
// 建立一個預設大小為 10 的向量
public Vector()
// 建立指定大小的向量
public Vector(int initialCapacity)
// 建立指定大小的向量,並且指定增量。增量表示向量每次增加的元素數目
public Vector(int initialCapacity, int capacityIncrement)
// 建立一個包含集合 c 元素的向量
public Vector(Collection<? extends E> c)
Vector 的資料結構和 ArrayList 差不多,它包含了三個成員變數:
// 存放元素的動態陣列
protected Object[] elementData;
// 動態陣列的實際大小
protected int elementCount;
// 動態陣列的增長係數
protected int capacityIncrement;
隨著 Vector 中元素的增加,Vector 的容量也會動態增長,capacityIncrement 是與容量增長相關的增長係數,具體增長細節在 grow 函式中,和 ArrayList 類似
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 如果 capacityIncrement > 0,新的容量大小 = 舊的容量大小 + 增長係數
// 否則容量擴大為原來的兩倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
Stack
Stack 是 Vector 的子類,實現了一個標準的後進先出的棧。Stack 也是通過陣列實現的,當然了,我們也可以將 LinkedList 當作棧來使用
Stack 只定義了預設建構函式,用來建立一個空棧
public Stack()
Stack 除了具有 Vector 的所有 API,還有自己實現的方法
// 判斷堆疊是否為空
public boolean empty()
// 檢視堆疊頂部的物件,但不從堆疊中移除它
public synchronized E peek()
// 移除堆疊頂部的物件,並作為此函式的值返回該物件
public synchronized E pop()
// 把物件壓入堆疊頂部
public E push(E item)
// 返回物件在堆疊中的位置,以 1 為基數
public synchronized int search(Object o)
Stack 的擴容機制基於 Vector,不過由於沒有指定增長係數,所有預設為 0,每次擴容陣列長度增大為原來的兩倍