ArrayList是JAVA的一種泛型容器,它實際上是一個用數組實現的鏈表,今天解析一下ArrayList的源碼,並寫個簡單的實現。
ArrayList的依賴關係
首先我們來看ArrayList的依賴繼承關係:
通過ArrayList繼承實現的接口說明了該類具有以下特性:
- ArrayList具有鏈表的特性
- Collection接口表示ArrayList是一個數據集合,但是這個數據集合沒有位置和順序關係,因此ArrayList實現了類似isEmpty,contains的方法
- RandomAccess接口說明了ArrayList支持隨機訪問,這是因爲底層是用數組實現,天然支持隨機訪問
- Iterable接口表示ArrayList支持迭代訪問,可以使用foreach語法進行數據的遍歷
- Serializable接口表示ArrayList的數據支持序列化
- Cloneable接口表示ArrayList支持對象的克隆
ArrayList原理解析
ArrayList是通過數組實現,該類內部定義了一個Object數組用於存放數據,由於存放的數據類型是一個泛型,因此只能將數組定義爲Object[]類型,這是因爲Object類是所有類的父類,因此不管什麼類型的對象都能賦值給Object的變量。
transient Object[] elementData;
public ArrayList(int initialCapacity)
public ArrayList()
public ArrayList(Collection<? extends E> c)
ArrayList的構造函數有三種,如果沒有給定初始的數組大小,那麼將創建一個空的數組;如果給定數組初始大小,那麼將創建對應大小的數組;也支持給定一個Collection對象,那麼該對象內的數據將賦值到elementData數組內。
ArrayList數據的添加
ArrayList調用add方法添加數據時,會先檢查一下當前數組大小是否具有足夠的空間。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
當數組空間不足時,最終會跳轉到grow方法進行數組擴容,該方法是計算原空間大小*1.5與minCapacity之間的最大值作爲新的數組的大小,然後將原數組的數據都拷貝至新的數組。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList數據的刪除
ArrayList的底層實現是數組,數據連續排放,所以在刪除數據時,效率較低,需要進行數據的挪動。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
假設數組大小是size,要刪除的數據下標是index,那麼下標大於index的數據都要向左挪動一位,並將原來的最後一位置爲空。System.arraycopy執行的功能實際上就是進行數據的平移。
ArrayList轉數組
由於Array的底層實現是一個數組,因此,ArrayList可以將維護的數組返回,但是由於這是一個泛型容器,如果直接將底層的Object[]數組強制轉化爲T[]返回,編譯器會報錯,java不允許父類對象強轉爲子類變量。
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
toArray方法實現對具體類型數組的轉化,該方法有一個T[] a的參數,如果a的大小足夠存放所有數據,那麼直接進行數據的複製並返回結果。
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
如果a數組的大小不夠大,那麼需要創建一個新的T[]數組進行數據的複製。Array.newInstance(newType.getComponentType(), newLength)是利用了反射,通過給定的對象類型,創建一個指定大小,指定對象類型的數組,因爲創建的數組本身就是T[]類型的,因此這裏的強轉是沒有問題的。