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