ArrayList是日常開發中經常使用到的集合,其底層採用陣列實現,因此元素按序存放。其優點是可以使用下標來訪問元素,時間複雜度是O(1)。其缺點是刪除和增加操作需要使用System.arraycopy()來移動部分受影響的元素,時間複雜度為O(N)。同時ArrayList由於是採用陣列來存放資料,因此會有容量限制,在使用時需要擴容,當插入操作超出陣列長度,就會進行擴容,擴容後陣列的長度為原來的1.5倍,預設的陣列長度是10。

為了更好的掌握ArrayList,因此閱讀並仿照ArrayList原始碼,實現一個具有增刪改查以及自動擴容功能的簡易版MyArrayList(程式碼幾乎與ArrayList原始碼一致)。

首先新建一個class,命名為MyArrayList

public class MyArrayList<E> {
}

由於ArrayList是通過陣列來儲存元素的,因此我們定義一個Object型別的陣列elementData來儲存資料,再定義一個變數size,用來記錄陣列中的元素個數,其預設值為0。

public class MyArrayList<E> {
private Object[] elementData; //ArrayList儲存元素的物件陣列
private int size; //ArrayList儲存元素的個數 }

接下來就是建構函式,有兩種,第一種是未指定初始容量的建構函式,預設容量為10;第二種是在建構函式中傳入指定容量。

先說第一種建構函式,ArrayList在預設情況下,其容量為10。因此我們定義一個常量DEFAULT_CAPACITY = 10作為預設容量。同時,還定義一個常量陣列DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}用於對elementData進行初始賦值。

public class MyArrayList<E> {
private Object[] elementData; //ArrayList儲存元素的物件陣列
private int size; //ArrayList儲存元素的個數 private final static int DEFAULT_CAPACITY = 10; //ArrayList的物件陣列的預設初始容量
private final static Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //ArrayList物件陣列的預設初始化 /**
* 不指定初始容量的建構函式
*/
public MyArrayList(){
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} }

需要注意的是這裡的預設容量10並不是在建構函式中直接使用,而是在第一次插入進行擴容時才會使用。

第二種建構函式,傳入指定的容量。根據傳入的容量引數,我們有以下三種結果:

①傳入的容量引數大於0:則以該引數為容量建立物件陣列

②存入的容量引數等於0:則需要建立一個空的物件陣列,因此定義一個常量陣列EMPTY_ELEMENTDATA = {}用於傳入容量為0時的初始化。

③傳入的容量引數小於0:明顯這是非法的,因此丟擲引數異常IllegalArgumentException()

public class MyArrayList<E> {
private Object[] elementData; //ArrayList儲存元素的物件陣列
private int size; //ArrayList儲存元素的個數 private final static int DEFAULT_CAPACITY = 10; //ArrayList的物件陣列的預設初始容量
private final static Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //ArrayList物件陣列的預設初始化
private static final Object[] EMPTY_ELEMENTDATA = {}; //傳入容量為0時的初始化 /**
* 不指定初始容量的建構函式
*/
public MyArrayList(){
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* 傳入指定初始容量的建構函式
* @param initialCapacity 傳入的初始化容量
*/
public MyArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("非法的容量: "+
initialCapacity);
}
}
}

好了,建構函式構建完畢,接下來就是增刪改查功能的實現,實現的方法如下:

//增,將元素放到陣列末尾元素的後面,e為待插入元素,返回boolean
boolean add(E e) //刪,刪除指定位置的元素,index為待刪除元素的位置,返回被刪除的元素
E remove(int index) //改,替換指定位置的元素,index為被替換元素的位置,e為替換的元素,返回被替換的元素
E set(int index, E e) //查,查詢指定位置的元素,index為查詢的位置,返回查到的元素
E get(int index)

首先是add(E e)方法,由於陣列容量有限制,因此我們新增一個元素,都有可能要進行擴容,所以我們需要編寫一個函式ensureCapacityInternal來判斷是否需要自動擴容,若需要則進行擴容。

 	/**
* ArrayList的add方法
* 將元素放到陣列末尾元素的後面
* @param e 待插入的元素
* @return
*/
boolean add(E e){
//1、自動擴容機制,傳入的是目前需要的最小容量
ensureCapacityInternal(size + 1);
}

ensureCapacityInternal函式中,需要傳入目前需要的最小容量。同時我們還要判斷物件陣列elementData是否為空陣列,若是,則將傳入的目前需要的最小容量與預設容量10進行對比,取其中的最大值作為本次擴容的容量。

/**
* 判斷原陣列是否為空陣列
* 是:則選預設容量和目前需要的最小容量二者中的最小值
* 否:則繼續往下判斷
* @param minCapacity 目前需要的最小容量
*/
void ensureCapacityInternal(int minCapacity){
// elementData 為空陣列,則將傳入的minCapacity與預設的初始容量DEFAULT_CAPACITY進行對比,取兩者中最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
} //接著往下判斷
ensureExplicitCapacity(minCapacity);
}

接下來,我們判斷是否需要進行擴容。如果目前需要的最小容量大於原陣列的長度,才進行擴容,否則不進行擴容,該功能寫入函式 ensureExplicitCapacity

    /**
* 判斷是否需要進行擴容
* 如果目前需要的最小長度大於原陣列的長度,才進行擴容
* 否則不進行擴容
* @param minCapacity 目前需要的最小容量
*/
void ensureExplicitCapacity(int minCapacity){
//目前需要的最小容量超過原陣列長度,才進行擴容,否則就不擴容
if (minCapacity - elementData.length > 0) {
grow(minCapacity); //擴容
}
}

然後,若進行擴容,則執行擴容函式grow。在grow中,我們需要進行如下操作:

①獲取原陣列的容量oldCapacity,然後計算出值為oldCapacity1.5倍的新容量newCapacity

int oldCapacity = elementData.length;
//擴容為原來的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);

②將擴容1.倍後的新容量newCapacity與目前需要的最小容量minCapacity進行對比,若新容量小於目前需要的最小容量,則新容量的值取目前需要的最小容量。

if (newCapacity - minCapacity < 0) newCapacity = minCapacity;

③將新容量newCapacity與所允許的陣列的最大長度進行對比,陣列最大長度定義為常量MAX_ARRAY_SIZE = Integer.MAX_VALUE,值為整數的最大值。如果新容量newCapacity大於陣列最大長度MAX_ARRAY_SIZE ,則取目前需要的最小容量minCapacity與陣列最大長度MAX_ARRAY_SIZE 兩者中的最小值作為新容量newCapacity的值。

 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = (minCapacity - MAX_ARRAY_SIZE) > 0 ? MAX_ARRAY_SIZE : minCapacity;

④使用Arrays.copyOf(原陣列, 新長度)進行陣列的複製,即實現陣列擴容

elementData = Arrays.copyOf(elementData,newCapacity);

完成擴容任務的函式grow如下:

 /**
* 擴容函式:如何進行擴容(擴多少)
* ①擴容1.5倍
* ②若擴容1.5倍還不滿足需要的最小容量,則擴容長度為目前需要的最小容量
* ③若新的容量大於陣列所允許的最大長度,則取需要的最小容量與陣列所允許的最大長度
* 兩者中的最小值
* @param minCapacity 目前需要的最小容量
*/
void grow(int minCapacity){
int oldCapacity = elementData.length;
//oldCapacity原陣列長右移1位,即相當於除2,最後加原長度,則為擴容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果擴容1.5倍後的新容量小於需要的最小容量,則新的容量即為傳入的最小容量
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
//如果新容量大於陣列能夠允許的最大長度,則看傳入的最小容量與陣列最大長度對比,取其中的小者
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = (minCapacity - MAX_ARRAY_SIZE) > 0 ? MAX_ARRAY_SIZE : minCapacity; //Arrays.copyOf(原陣列, 新長度),返回新陣列。使用該函式完成自動擴容
elementData = Arrays.copyOf(elementData,newCapacity);
}

至此,就完成了新增時,判斷是否需要擴容,並且完成擴容功能。接下來我們只需要將新增元素插入陣列元素末尾位置的下一個位置,並返回true即可。

boolean add(E e){
//1、自動擴容機制,傳入的是目前需要的最小容量
ensureCapacityInternal(size + 1); //2、擴容完畢,將元素存入
elementData[size++] = e; return true;
}

最終,新增add方法和自動擴容有關的函式編寫完成:

	/**
* ArrayList的add方法
* 將元素放到陣列末尾元素的後面
* @param e 待插入的元素
* @return
*/
boolean add(E e){
//1、自動擴容機制,傳入的是目前需要的最小容量
ensureCapacityInternal(size + 1); //2、擴容完畢,將元素存入
elementData[size++] = e; return true;
} /**
* 判斷原陣列是否為空陣列
* 是:則選預設容量和目前需要的最小容量二者中的最小值,然後接著往下判斷
* 否:則直接繼續往下判斷
* @param minCapacity 目前需要的最小容量
*/
void ensureCapacityInternal(int minCapacity){
// elementData 為空陣列,則將傳入的minCapacity與預設的初始容量DEFAULT_CAPACITY進行對比,取兩者中最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
} //接著往下判斷
ensureExplicitCapacity(minCapacity);
} /**
* 判斷是否需要進行擴容
* 如果目前需要的最小長度大於原陣列的長度,才進行擴容
* 否則不進行擴容
* @param minCapacity 目前需要的最小容量
*/
void ensureExplicitCapacity(int minCapacity){
//目前需要的最小容量超過原陣列長度,才進行擴容,否則就不擴容
if (minCapacity - elementData.length > 0) {
grow(minCapacity); //擴容
}
} /**
* 擴容函式:如何進行擴容(擴多少)
* ①擴容1.5倍
* ②若擴容1.5倍還不滿足需要的最小容量,則擴容長度為目前需要的最小容量
* ③若新的容量大於陣列所允許的最大長度,則取需要的最小容量與陣列所允許的最大長度
* 兩者中的最小值
* @param minCapacity 目前需要的最小容量
*/
void grow(int minCapacity){
int oldCapacity = elementData.length;
//oldCapacity原陣列長右移1位,即相當於除2,最後加原長度,則為擴容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果擴容1.5倍後的新容量小於需要的最小容量,則新的容量即為傳入的最小容量
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
//如果新容量大於陣列能夠允許的最大長度,則看傳入的最小容量與陣列最大長度對比,取其中的小者
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = (minCapacity - MAX_ARRAY_SIZE) > 0 ? MAX_ARRAY_SIZE : minCapacity; //Arrays.copyOf(原陣列, 新長度),返回新陣列。使用該函式完成自動擴容
elementData = Arrays.copyOf(elementData,newCapacity);
}

接下來,就是刪除,remove方法。由於該方法傳入待刪除元素的位置索引index,因此需要檢查index

的範圍是否符合要求。編寫一個函式rangeCheck來檢查下標。

	/**
* 檢查index範圍
* 超出範圍則丟擲異常
* @param index 陣列下標位置
*/
void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("哎呀,超出範圍了!");
}

index沒有超出範圍,則接下來就是獲取索引對應的元素,獲取方式很簡單,就是elementData[index]即可。考慮到其他方法也會需要通過這樣方式來獲取對應位置的元素,因此我們將這個操作抽取出來,成為一個函式elementData(),用於獲取元素。

 	/**
* 返回陣列中指定位置的元素
* @param index
* @return
*/
E elementData(int index){
return (E) elementData[index];
}

那麼,目前remove方法前面兩個操作我們已經完成

    E remove(int index){
//1、檢查index範圍
rangeCheck(index);
//2、獲取index對應的元素
E oldValue = elementData(index); }

刪除index元素,需要把該位置後面的所有元素都向前移動一個位置。因此接下來我們就需要將index後面的元素向前移動一個位置。

具體做法是,先計算出需要移動的元素個數numMoved,用陣列中最後一個元素的下標減去index即可獲得需要移動的元素個數:size-1-index

然後利用System.arraycopy()來移動元素,該方法的用法如下:

System.arrayCopy(Object srcArray,int srcPos,Object destArray ,int destPos,int length)

①Object srcArray 原陣列(要拷貝的陣列)

②int srcPos 要複製的原陣列的起始位置(陣列從0位置開始)

③ Object destArray 目標陣列

④ int destPos 目標陣列的起始位置

⑤int length 要複製的長度

從原陣列srcArray 取元素,範圍為下標srcPos到srcPos+length-1,取出共length個元素,存放到目標陣列中,存放位置為下標destPos到destPos+length-1。

我們將原陣列和目標陣列都設為elementData,然後原陣列的起始位置為index+1,目標陣列的起始位置為index,要複製的長度設為元素個數numMoved。這樣就能做到將陣列index位置後面的元素向前移動一位。

不過這樣做目標陣列的最後一位元素依然是原來的數,因此我們需要將目標陣列最後的元素置為null,並且由於是刪除,所以元素個數size需要減一。至此,刪除方法remove完成。

	/**
* ArrayList的remove方法
* @param index 要刪除元素的位置
* @return 返回被刪除元素
*/
E remove(int index){
//1、檢查index範圍
rangeCheck(index);
//2、獲取index對應的元素
E oldValue = elementData(index);
//3、計算需要移動的元素的個數
int numMoved = size - 1 - index;
//4、將index後面的數往前移動一位
if (numMoved > 0){
System.arraycopy(elementData,index + 1, elementData, index, numMoved);
}
//5、把最後的元素置為null
elementData[--size] = null;
//返回被刪除元素
return oldValue;
}

增刪操作已完成,接下來就是改操作,set()方法。這個方法就比較簡單,具體的步驟如下:

①檢查index範圍

②獲取index位置的元素

③將index位置的元素,替換為傳入的元素

④返回原先index位置的元素

	/**
* ArrayList的set
* @param index 需要修改的位置
* @param e 需要替換的元素
*/
E set(int index, E e){
//1、檢查index範圍
rangeCheck(index); //2、獲取指定位置的元素
E oldValue = elementData(index); //3、替換元素
elementData[index] = e; //4、返回原先index位置的元素
return oldValue;
}

最後,就是查操作,get方法。該方法更為簡單,只需要先檢查index範圍,再獲取index位置的元素直接返回即可。

	/**
* ArrayList的get方法
* @param index
* @return
*/
E get(int index){
//1、檢查index範圍
rangeCheck(index); //2、獲取指定位置的元素
return elementData(index);
}

到這裡,我們編寫的簡易版ArrayList的增刪改查操作就全部完成了。點進JDK1.8中ArrayList原始碼可以看到,我們的上面的程式碼幾乎與ArrayList原始碼一模一樣。

最終這個簡易版ArrayList所有程式碼如下:

public class MyArrayList<E> {

    private int size; //ArrayList中實際元素的數量的size
private Object[] elementData; //ArrayList的物件陣列 private final static int DEFAULT_CAPACITY = 10; //ArrayList的物件陣列的預設初始容量
private final static int MAX_ARRAY_SIZE = Integer.MAX_VALUE; //陣列的最大長度,也就是整數的最大值
private final static Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //ArrayList的物件陣列的預設初始化
private static final Object[] EMPTY_ELEMENTDATA = {}; //傳入容量為0時的初始化 /**
* 不指定初始容量的建構函式
*/
public MyArrayList(){
elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* 傳入指定初始容量的建構函式
* @param initialCapacity
*/
public MyArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("非法的容量: "+
initialCapacity);
}
} /**
* ArrayList的add方法
* 將元素放到陣列有效長度的末尾
* @param e 待插入的元素
* @return
*/
boolean add(E e){
//1、自動擴容機制,傳入的是目前需要的最小容量
ensureCapacityInternal(size + 1); //2、擴容完畢,將元素存入
elementData[size++] = e; return true;
} /**
* 判斷原陣列是否為空陣列
* 是:則選預設容量和目前需要的最小容量二者中的最小值,然後接著往下判斷
* 否:則直接繼續往下判斷
* @param minCapacity 目前需要的最小容量
*/
void ensureCapacityInternal(int minCapacity){
// elementData 為空陣列,則將傳入的minCapacity與預設的初始容量DEFAULT_CAPACITY進行對比,取兩者中最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
} //接著往下判斷
ensureExplicitCapacity(minCapacity);
} /**
* 判斷是否需要進行擴容
* 如果目前需要的最小長度大於原陣列的長度,才進行擴容
* 否則不進行擴容
* @param minCapacity 目前需要的最小容量
*/
void ensureExplicitCapacity(int minCapacity){
//目前需要的最小容量超過原陣列長度,才進行擴容,否則就不擴容
if (minCapacity - elementData.length > 0) {
grow(minCapacity); //擴容
}
} /**
* 擴容函式:如何進行擴容(擴多少)
* ①擴容1.5倍
* ②若擴容1.5倍還不滿足需要的最小容量,則擴容長度為目前需要的最小容量
* ③若新的容量大於陣列所允許的最大長度,則取需要的最小容量與陣列所允許的最大長度
* 兩者中的最小值
* @param minCapacity 目前需要的最小容量
*/
void grow(int minCapacity){
int oldCapacity = elementData.length;
//oldCapacity原陣列長右移1位,即相當於除2,最後加原長度,則為擴容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果擴容1.5倍後的新容量小於需要的最小容量,則新的容量即為傳入的最小容量
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
//如果新容量大於陣列能夠允許的最大長度,則看傳入的最小容量與陣列最大長度對比,取其中的小者
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = (minCapacity - MAX_ARRAY_SIZE) > 0 ? MAX_ARRAY_SIZE : minCapacity; //Arrays.copyOf(原陣列, 新長度),返回新陣列。使用該函式完成自動擴容
elementData = Arrays.copyOf(elementData,newCapacity);
} /**
* ArrayList的remove方法
* @param index 要刪除元素的位置
* @return 返回被刪除元素
*/
E remove(int index){
//1、檢查index範圍
rangeCheck(index);
//2、獲取index對應的元素
E oldValue = elementData(index);
//3、計算需要移動的元素的個數
int numMoved = size - 1 - index;
//4、將index後面的數往前移動
if (numMoved > 0){
System.arraycopy(elementData,index + 1, elementData, index, numMoved);
}
//5、把最後的元素置為null
elementData[--size] = null;
//返回被刪除元素
return oldValue;
} /**
* 檢查index範圍
* 超出範圍則報錯
* @param index
*/
void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("哎呀,超出範圍了!");
} /**
* 返回陣列中指定位置的元素
* @param index
* @return
*/
E elementData(int index){
return (E) elementData[index];
} /**
* ArrayList的set
* @param index 需要修改的位置
* @param e 需要替換的元素
*/
E set(int index, E e){
//1、檢查index範圍
rangeCheck(index); //2、獲取指定位置的元素
E oldValue = elementData(index); //3、替換元素
elementData[index] = e; //4、返回原先index位置的元素
return oldValue;
} /**
* ArrayList的get方法
* @param index
* @return
*/
E get(int index){
//1、檢查index範圍
rangeCheck(index); //2、獲取指定位置的元素
return elementData(index);
} /**
* 獲取元素個數
* @return
*/
int size(){
return size;
} }

我們測試一下,這個簡易版ArrayList

 public static void main(String[] args) {
MyArrayList<String> myArrayList = new MyArrayList<>();
//增
myArrayList.add("hello");
myArrayList.add("word");
myArrayList.add("hello");
myArrayList.add("java");
//改
myArrayList.set(1,"####");
//刪
myArrayList.remove(2);
//查
for (int i = 0; i < myArrayList.size(); i++){
System.out.println(myArrayList.get(i));
} }

測試結果如下:

hello
####
java