Java ArrayList原始碼分析(有助於理解資料結構)
arraylist原始碼分析
1.陣列介紹
陣列是資料結構中很基本的結構,很多程式語言都內建陣列,類似於資料結構中的線性表
在java中當建立陣列時會在記憶體中劃分出一塊連續的記憶體,然後當有資料進入的時候會將資料按順序的儲存在這塊連續的記憶體中。當需要讀取陣列中的資料時,需要提供陣列中的索引,然後陣列根據索引將內
存中的資料取出來,返回給讀取程式。在Java中並不是所有的資料都能儲存到陣列中,只有相同型別的資料才可以一起儲存到陣列中。
因為陣列在儲存資料時是按順序儲存的,儲存資料的記憶體也是連續的,所以他的特點就是:定址讀取資料比較容易,插入和刪除比較困難。
帶參構造方法
public ArrayList(int initialCapacity) { if (initialCapacity > 0) {
//建立指定長度的陣列 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {
//空陣列 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);} //如果既不大於零,也不小於零,就會報錯丟擲異常 }
空參構造方法
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //給一個成員變數賦值 }
private static final Object[] DEFAULTCAPACITY EMPTY ELEMENTDATA={}; //空參構造方法建立的陣列就是一個空的
下面這個用的不多,能看懂即可
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); //把Collection這個集合也變成了一個數組,傳給了當前陣列elementData//大於0,就拷貝陣列 if ((size = elementData.length) != 0) { //判斷當前elementData陣列的長度,如果不等於0 if (elementData.getClass() != Object[].class) //判斷型別是不是Object陣列型別,如果不是這個型別 elementData = Arrays.copyOf(elementData, size, Object[].class); //拷貝一個數組,然後長度,然後把Object[].class陣列傳進去,然後生成一個elementData陣列
//不是大於0,就拷貝一個空的 } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; //設定為空陣列 } }
新增元素
public boolean add(E e) {
//檢測是否需要擴容 下¹ensureCapacityInternal(minCapacity:size + 1); // size是當前存的資料個數
//陣列賦值
elementData[size++] = e; return true; }
擴容的入口方法,計算容量
private void 上¹ensureCapacityInternal(int minCapacity){ //minCapacity最小值,傳的時候就是+1後的 下下³ensureExplicitCapacity(下²calculateCapacity(elementData,minCapacity)); //計算容量,傳入一個數組elementData,再傳一個最小容量minCapacity
計算最小容量
private static int 上²calculateCapacity(Object[] elementData, int minCapacity){ //如果elementData是空的 if(elementData==DEFAUL TCAPACITY EMPTY EL EMENTDAT)| //返回一個預設或者minCapacity,最後是返回其中最大的 return Math. max(DEFAULT_CAPACIT minCapacity); //不是空,就返回size+1的值 return minCapacity; }
private void 上上³ensureExplicitCapacity(int minCapacityp{
//只要發生修改就+1 modCount++;
//是否滿足擴容的條件 最小容量(當前陣列存值得容量?) - object陣列的長度
if(minCapacity-elementData. length〉0)//elementData.lenth=10 (預設容量為10) 下¹grow(minCapacity);
modCount++ : 該欄位表示list結構上被修改的次數。在對集合進行新增或移除操作時會使modCount+1,這是一個記錄集合變化次數的變數。
作用是在使用迭代器Iterator對集合進行遍歷時,用modCount來判斷集合內資料沒有發生變化,否則會丟擲異常。
陣列擴容方法
private void 上¹grow(int minCapacity){ //計算當前陣列容量,也就是老的容量
int oldCapacity=elementData. length;
//新的陣列容量 = 老容量 + 老容量/2 (老容量的1.5倍)
int newCapacity=oldCapacity+(oldCapacity >>1); >>1 除二
// oldcapacity=10
// oldcapacity >>1
// 0000 1010 >> 1
// 0000 0101 = 5
//判斷新的陣列容量-之前最小容量
if(newCapacity-minCapacity<0) newCapacity=minCapacity;
if (newCapacity - MAX_ARRAY_SIZE>0) //MAX_ARRAY_SIZE:值特別大,如果新的陣列比它還大,就越界了,或者直接返回最大長度 newCapacity = hugeCapacity(minCapacity);
elementData=Arrays. copyof(elementData, newCapacity);
public void add(int index, E element) {
//判斷下標是否越界 下1rangeCheckForAdd(index); //檢測是否需要擴容 ensureCapacityInternal(size + 1);
//把index之後的所有元素向後移動一位
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//將index位置覆蓋上新值
elementData[index] = element; size++; }
private void 上1rangeCheckForAdd(int index){ if(index>size || index<0) throw new IndexOutofBoundsException(outofBoundsMsg(index));
System.arraycopy(elementData, index, elementData, index + 1,size - index); 舉例
int[] elementData = new int[10]; for(int i=0;i<5;i++){ //陣列的下標 0,1,2,3,4 elementData[i]=i+1; //陣列的值 1,2,3,4,5 System. out. println("=="+Arrays. toString(elementData)); int size=5; int index=1; //插入陣列下標位置 System. arraycopy(elementData, index, elementData, destPos: index+1, length: size-index); 原陣列,插入指定的位置,又指定的陣列,指定的位置為當前位置+1,到5-1=4的位置 >>從插入位置往後算 System. out. println("--"+Arrays. toString(elementData));
列印結果
== [1,2,3,4,5,0,0,0,0,0]
一 [1,2,2,3,4,5,0,0,0,0] 2:是要插入的數值
刪除方法
指定位置刪除
public E remove(int index) {
檢測當前下標是否越界 下1rangeCheck(index); modCount++; E oldValue = elementData(index); 將index+1及之後的元素向前移動一位,覆蓋被刪除的值 int numMoved = size - index - 1; if (numMoved > 0) 下下2System.arraycopy(elementData, index+1, elementData, index, numMoved);
將最後一個位置的元素清空
elementData[--size] = null; return oldValue; }
private void 上1rangeCheck(int index){ if(index >=size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
上上2 System.arraycopy(elementData, index+1, elementData, index, numMoved); 舉例int[] elementData = new int[10];
for(int i=0;i<10;i++){ //陣列的下標 0,1,2,3,4,5,6,7,8,9 elementData[i]=i+1; //陣列的值 1,2,3,4,5,6,7,8,9,10
}
int size = elementData.lengh; //數組裡有幾個數,長度就是幾 elementData.lengh = 10
int index = 2;
int numMoved = size - index - 1; System. out. println("==" + Arrays. toString(elementData)); System. arraycopy(elementData, srcPos: index+1, elementData, index, numMoved); 原陣列,指定的位置為要刪除的下標往後一個位置,又指定的陣列,刪除位置的下標,10-2-1=7 >>從指定刪除下標位置往後數,全部向前移動一位
System. out. println("--" + Arrays. toString(elementData)); 列印結果 == [1,2,3,4,5,6,7,8,9,10] 3:要刪除的數字 一 [1,2,4,5,6,7,8,9,10,10]
指定元素刪除
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { 下1fastRemove(index); return true;} //從0開始遍歷,大小為陣列長度,找到後直接刪除 } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } }
如果沒有匹配元素,返回false return false; }
快速刪除, 沒有檢測index下標的原因:因為之前需要指定下標刪除,現在是直接指定刪除某個元素,如果元素不存在,for語句遍歷也不會找到這個元素,所有,只要能滿足兩個if語句其中一個,那這個元素一定在我的合理範圍內的,所以就不需要檢測有沒有下標
快速刪除,沒有檢測index下標
private void 上1fastRemove(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 }
&n