最近仔細的研究了下集合框架的原始碼,並完全手寫下來,這裡將每一步的進展記錄下來,歡迎指點。
arraylist的構造器,分別為無參構造器、傳入初始大小的有參構造器、傳入乙個集合型別的有參構造器,這裡只介紹前面兩種常用的構造器。
無參構造器:
arraylist的底層實際就是使用陣列來實現的,我們建立乙個陣列,用於之後對arraylist的操作。
//arraylist底層就是用object陣列實現的
private object elementdata;
建立乙個空的object陣列,用於無參構造器使用
//宣告乙個空的陣列,用於空構造器的初始化
private static final object defaultcapacity_empty_elementdata = {};
宣告無參構造器時,沒有指定大小,在add方法時,才進行了初始化容量,這裡直接將空陣列的位址,賦給之後需要運算元組即可。
//未指定初始容量大小的構造器
public exarraylist()
帶初始化的有參構造器:
這裡需要判斷傳入值的乙個正確性,注意這裡之所以將initialcapacity等於0的邏輯單獨抽出來,而沒有new object的原因是,這樣做會導致add方法的判斷錯誤,所以直接等於我們建立的空陣列的位址。
//指定初始容量大小的構造器
public exarraylist(int initialcapacity) else if (initialcapacity == 0) else
}
新增方法作為我們最常用的乙個方法,其實內部原理非常簡單,但設計的非常巧妙,這裡介紹四個新增方法,add(e e)、add(int index,object element)、addall(collection c)、add(int index,collection c)
add(e e):
所有的新增方法在新增前,都需要判斷當前的乙個容量大小是否足夠,即陣列在宣告時,大小都是固定了的,新增乙個元素,就需要判斷容量是否足夠,如果不夠就進行乙個擴容,然後在新增元素。
傳入的引數表示擴容的乙個最小大小,即當前使用大小加一。
public boolean add(e e)
size是記錄當前陣列實際使用的大小,即陣列裡有多少資料,新增乙個元素會加一,刪除則會減一。
//記錄當前陣列實際使用大小
private int size;
這裡就可以知道,當時帶初始化的有參構造器在判斷時,為什麼將等於0的邏輯分離出來了,如果當時是直接new object,下面的if條件就不滿足了,最小容量就還是1,影響之後的乙個擴容。
/**
* @param mincapacity 最小擴容大小(即當前實際使用容量大小+1)
*/private void ensurecapacityinternal(int mincapacity)
ensureexplicitcapacity(mincapacity);
}
在新增元素的時候,如果elementdata還是乙個空陣列,之後就會將其擴容為大小為10的陣列。
//預設的初始容量大小
private static final int default_capacity = 10;
這個方法就是在判斷是否需要進行擴容,如果你傳入的最小擴容量是大於當前陣列的長度時,就需要進行擴容。
private void ensureexplicitcapacity(int mincapacity)
modcount 引數是用於記錄修改的次數,主要是用於使用迭代器進行迴圈輸出時,如果你在迴圈中,對arraylist進行新增或刪除的操作時,會導致modcount的迴圈前和迴圈後的值不一致,然後丟擲乙個併發異常的錯。
//修改次數,防止併發修改
protected transient int modcount = 0;
擴容的策略預設是為之前大小的1.5倍, arrays.copyof是將elementdata的大小變為newcapacity,並且保留elementdata裡的資料。
//擴容
private void grow(int mincapacity)
if (newcapacity - max_array_size > 0)
newcapacity = hugecapacity(mincapacity);
//將陣列的大小變為newcapacity,值依舊存在
elementdata = arrays.copyof(elementdata, newcapacity);
}
max_array_size 陣列允許的最大容量
//陣列允許的最大容量(減8的原因是因為陣列需要8個bytes去儲存自己的大小)
private static final int max_array_size = integer.max_value - 8;
一系列操作完畢後,就將物件新增到陣列中去,並且size加一。
add(int index, object element):
指定下標插入物件
public void add(int index, object element)
大致的思路和直接新增乙個物件一樣,只不過需要判斷下標值的準確性
private void rangecheckforadd(int index)
之後判斷是否需要擴容,然後將你指定的下標後的元素,全部向後複製,為之後插入的值騰位置,並且保持其餘元素值不變
arraycopye方法:
//① 源陣列 ② 源陣列開始複製的下標 ③ 複製後目標陣列
//④ 目標陣列開始複製的下標 ⑤ 源陣列需要複製的長度
system.arraycopy(elementdata, index, elementdata, index + 1,
size - index);
之後在將指定下標的元素,替換為需要插入的元素,實際使用的陣列大小加一即可。
addall(collection c)
將傳入的集合裡的元素,都新增到elementdata陣列中,修改實際使用大小,完成新增
首先將該集合轉為陣列,獲取該陣列的長度,然後按照慣例判斷當前陣列的容量是否足夠,這裡傳入的最小容量的引數就不是size+1,而是size+需要插入的元素個數,然後使用arraycopy方法將元素都複製到elementdata中去,之後修改實際使用大小。
public boolean addall(collection c)
addall(int index, collection c):
在指定下標中,插入乙個集合的元素,其實這個方法等於就是前面兩個方法的結合體了
和前乙個方法比,多了兩步,乙個是判斷需要插入的集合,是否可以直接在當前陣列的尾巴後面插入,如果是直接在尾部新增元素,即直接複製元素即可,如果是在中間插入元素的話,就需要將源陣列的中間的元素給向後複製需要新增的元素的個數字,然後在覆蓋元素。
public boolean addall(int index, collection c)
//5.將傳入的集合中所有的元素,複製到elementdata中
system.arraycopy(a, 0, elementdata, index, numnew);
//6.修改實際使用長度
size = size + numnew;
return numnew != 0;
}
刪除方法的話,在原始碼中也是有兩種形式,乙個是根據下標刪除,乙個是根據物件刪除,後面的方面過於呆板,不進行解釋了。
elementdata(int index):
獲取值,即根據對應的下標返回對應的元素
private e elementdata(int index)
remove(int index):
我們可以發現,在對陣列的操作時,尤其是增刪對中間元素操作時,都是需要移動元素,來騰出位置或直接覆蓋某個元素,來達到目標的,這也就是為什麼陣列比鍊錶的增刪操作慢的乙個原因,並且陣列在宣告時就需要申請一段連續的空間記憶體來存值,申請的空間過大之後又可能會造成浪費,而過小的話動不動就需要擴容。
public e remove(int index)
//5.將最後乙個元素置為null
elementdata[--size] = null;
return oldvalue;
}
之後會介紹linkedlist的原始碼,可以發現兩者的優缺點。 純手寫ArrayList集合 二
在閱讀arraylist的jdk原始碼的時候,你經常會看到這兩個系統函式 arrays.copyof elementdata,size 而這個方法的原始碼是 public static t copyof t original,int newlength public static t copyof ...
手寫ArrayList入門
arraylist是集合的一種實現,實現了介面list,list介面繼承了collection介面。collection是所有集合類的父類。arraylist使用非常廣泛,不論是資料庫表查詢,excel匯入解析,還是 資料爬取都需要使用到,了解arraylist原理及使用方法顯得非常重要。那麼arr...
Java集合 ArrayList集合
以陣列實現。節約空間,但是陣列有容量限制。超出限制時會增加50 容量,用system.arraycopy 複製到新的陣列,因此最好能給出陣列大小的預估值。預設第一次插入元素時建立大小為10 的陣列。按照陣列下標來訪問元素 get i set i,e 的效能很高,這是陣列的基本優勢。直接在陣列末尾加入...