對於集合的原始碼分析,一般我會採用這幾種方式
- 怎麼新增元素?
- 怎麼獲取元素?
- 怎麼刪除元素?
- 內部資料結構實現?
話不多說,直接走起。
一.怎麼新增元素?
一般我們通過ArrayList新增元素。一般會呼叫其構造方法,然後呼叫其物件的add方法
檢視空參建構函式
//Constructs an empty list with an initial capacity of ten.
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
複製程式碼
通過建構函式可以發現。ArrayList在呼叫無參的建構函式時,會構造一個長度為10的快取陣列
檢視add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
複製程式碼
通過該方法發現 ArralyList內部的資料結構其實是一個陣列(elementData[size++] = e;)並且在新增時會先判斷當前容器在新增了一個物件之後該物件的容納能力(主要為了,在下一次新增元素的時候,快取陣列能夠有足夠的空間新增元素)。之後將元素新增到陣列末尾。
繼續檢視ensureCapacityInternal()方法
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
複製程式碼
這裡我們發現,如果當前elementData為空的話,minCapacity=DEFAULT_CAPACITY,同時DEFAULT_CAPACITY的預設值是10,從這我們可以看出,在第一次初始化的時候,ArrayList內部會預設建立一個內部長度為10的陣列。
繼續點選ensureExplicitCapacity()方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判斷新增元素後,快取陣列時候需要擴充套件
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
複製程式碼
該方法會記錄當前陣列的更改次數,並且判斷當前陣列新增後,是否需要進行增長,
繼續走grow方法(重點的來了!!!)
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//擴充套件陣列的長度,
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
複製程式碼
該方法會擴充套件快取為當前陣列的長度為 原陣列長度+原陣列長度的二分之一,也就是按照原陣列的50%進行增長,同時該陣列最大的擴充套件長度是Integer.MAX_VALUE - 8。也就是ArrayList最多能儲存的資料長度,通過擴充套件陣列長度以後,在下一次新增資料的時候,ArrayList就有足夠的空間去新增新的元素了。
二.怎麼獲取元素
其實ArrayList獲取其中的元素很簡單,根據角標獲取對應陣列中的元素,具體程式碼如下:
public E get(int index) {
if (index >= size)//判斷當前角標長度是否超過陣列長度,如果是丟擲異常,反之返回資料
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
複製程式碼
三.怎麼刪除元素
在ArrayList中,有兩個關於刪除元素的方法,一個是remove(int),另一個是remove(Object)
1.remove(int)方法
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 將陣列最後一位置null
return oldValue;
}
複製程式碼
這裡先判斷刪除角標是否超過陣列長度,然後通過System.arrayCopty()方法將角標對應的元素刪除。
這裡對System.arrayCopty()方法解釋一下。該方法的第一個引數是源陣列,第二個引數是複製的開始角標,第二個引數是目標陣列。第三個引數是目標陣列與源陣列的複製資料開始角標。最後一個引數是複製的長度。(注意:!!!複製的長度不能大於目標陣列減去開始角標的長度或源陣列減去開始角標的長度)
eg:
int[] a = {0, 1, 2, 3, 4};
int[] b = {5, 6, 7, 8, 9};
System.arraycopy(a, 0, b, 1, 3);
// 則進行操作後 b = {5,0,1,2,9}
複製程式碼
2.remove(Object)方法
public boolean remove(Object o) {
if (o == null) {//判斷當前元素是否為空,遍歷陣列,獲取其角標
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
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
}
複製程式碼
remove(Object)根據object在陣列的角標,執行fastRemove(index)方法。刪除方法與remoIndex(int)一樣。這裡就不在分析了。
總結
- ArrayList內部實現是陣列,且當陣列長度不夠時,陣列的會進行原陣列長度的1.5倍擴容。
- ArrayList內部元素是可以重複的。且有序的,因為是按照陣列一個一個進行新增的。
- ArrayList是執行緒不安全的,因為其內部新增、刪除、等操作,沒有進行同步操作。
- ArrayList增刪元素速度較慢,因為內部實現是陣列,每次操作都會對陣列進行復制操作,複製操作是比較耗時的
最後,附上我寫的一個基於Kotlin 仿開眼的專案SimpleEyes(ps: 其實在我之前,已經有很多小朋友開始仿這款應用了,但是我覺得要做就做好。所以我的專案和其他的人應該不同,不僅僅是簡單的一個應用。但是,但是。但是。重要的話說三遍。還在開發階段,不要打我),歡迎大家follow和start