ArrayList 源碼手記
從大學到現在ArrayList源碼看了忘忘了看,今天有空記錄下自己看的東西以加深印象。下面開始按照自己的想法開始分析ArrayList源碼。
1、先記錄下ArrayList的類繼承關係圖
2、下面記錄下ArrayList所實現接口中的三個標籤接口,大部分人只熟悉第一個標籤接口
//先看下ArrayList的定義形式,實現了四個接口,其中後三個爲標籤接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}
// 標籤接口一,大家最熟悉的序列化接口
public interface Serializable {
}
// 標籤接口二,隨機訪問接口,暗示這個類支持快速(通常是常量時間)隨機訪問
public interface RandomAccess {
}
// 標籤接口三,克隆接口
public interface Cloneable {
}
3、再來看下ArrayList的屬性有哪些
// 默認初始化容量,若是無參構造的ArrayList,在添加第一個元素時候會將容量膨脹到下面大小
private static final int DEFAULT_CAPACITY = 10;
// 這個元素數組是靜態的,那麼全局唯一資源,在指定容量爲0時或者其他幾個場景會讓elementData賦值爲此數組
private static final Object[] EMPTY_ELEMENTDATA = {};
// 這個數組按照我個人理解,類似一個標記數組,當無參化構造了ArrayList後,會將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在調用ArrayList的add方法添加第一個元素時候會判斷element是不是和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一引用,若是,就嘗試將數組快速膨脹到DEFAULT_CAPACITY容量大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 這個是實際存儲元素的數組
transient Object[] elementData; // non-private to simplify nested class access
// 這個字段是實際元素的個數
private int size;
// 最大數組大小是2147483647 - 8 個
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
4、接下來看看ArrayList的構造函數們
// 構造方法一,無參構造方法,將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//爲什麼只有在無參構造函數時纔將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA還暫未理解,猜想可能是帶參構造函數裏面的容量不能隨意擴容,以防改變使用者原意。
}
// 構造方法二,帶指定容量參數的構造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 在這裏若容量指定爲0的時候,把elementData賦值爲EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 構造函數三,帶集合參數的構造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 在這裏若集合參數內容爲空,則讓elementData也指向EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
5、ArrayList 函數分析,先從常用的函數分析
5.1 add(E e)
// 先分析add方法,add方法將給定元素添加到elementData末尾,返回值爲boolean類型
public boolean add(E e) {
// 第一步先驗證容量和統計修改次數
ensureCapacityInternal(size + 1); // Increments modCount!!
// 末尾添加給定值,size自增1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 這裏有兩個函數調用,下面逐一分析
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 在這一步可以看出,若elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一引用,那麼在添加第一個元素時候minCapacity就是1,肯定返回 DEFAULT_CAPACITY
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 修改次數加1
modCount++;
// 當minCapacity大於實際數組長度時候擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 擴容代碼
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 新容量爲實際元素長度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新的容量還小於minCapacity,那就讓新容量等於minCapacity,如果minCapacity值溢出上界,那麼下面這個判斷就不會執行。還有好幾種情況如兩個都溢出等等。
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 最後將調用Arrays.copy將生成一個新容量大小的數組,並將原來數組值放入,底層調用System.copy(src,srcPos,des,desPos,length)方法
elementData = Arrays.copyOf(elementData, newCapacity);
}
5.2 add(int index, E element)
// 在指定索引位置添加元素
public void add(int index, E element) {
rangeCheckForAdd(index);
// 容量檢查,在上面已經分析過
ensureCapacityInternal(size + 1); // Increments modCount!!
// 這段就不多說了,將index位置及以後所有元素統一向後挪一個位置
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
// add方法的範圍檢查
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
5.3 addAll(Collection<? extends E> c) 、addAll(int index, Collection<? extends E> c)
// 將一個集合所有元素添加到ArrayList中
public boolean addAll(Collection<? extends E> c) {
// 調用collection的通用方法toArray將集合轉爲數組
Object[] a = c.toArray();
// 獲取數組的長度,並進行擴容判斷
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
5.4 get(int index)
// 第二個比較常用的方法就是獲取元素的方法get
public E get(int index) {
// 先做索引範圍檢查
rangeCheck(index);
// 然後返回索引位置元素
return elementData(index);
}
private void rangeCheck(int index) {
// 如果索引大於實際數組長度就拋異常
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
5.5 contains(Object o)
// 個人感覺第三常用的方法就是用contains判斷一個元素是否存在數組中
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//這個方法是找元素在數組中首次出現的索引,被contains使用
public int indexOf(Object o) {
// 如果給定值爲null,那麼遍歷數組元素看哪個元素的值爲null
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 若給定值 != null 遍歷數組逐個對比
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
// 沒找到返回-1
return -1;
}
// 這個是查找元素最後一次出現的索引值,這個就是從數組最後面的元素向前遍歷
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
5.6 remove(int index)
// 下來比較常用的方法就是remove方法,移除指定位置的元素
public E remove(int index) {
// index範圍檢查
rangeCheck(index);
// 修改次數自增一
modCount++;
// 獲取要移除索引位置的元素
E oldValue = elementData(index);
// 總共要移動的元素的數量
int numMoved = size - index - 1;
// 如果要移動的數量大於0,那麼index之後的所有元素向前移一個位置
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
// 返回要移除的元素
return oldValue;
}
// 這個方法是移除指定元素,因爲要判斷數組裏面是否存在指定元素,所以算法複雜度O(n)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 先找到指定元素的索引,如果找到索引位置了,那麼移除時候就不需要索引範圍檢查,所以叫fastRemove
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 省略範圍檢查的步驟,直接移動數組,刪除指定位置元素
private void fastRemove(int index) {
modCount++;
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
}
以上就是ArrayList的主要方法,在ArrayList內部還有一些不常用的方法和內部類。現在可以看出ArrayList內部基於一個對象數組存放元素,因此擁有數組的特性: 定位快,但是查找需要O(n)複雜度,刪除需要挪動的元素較多。