【成都校區*精品*Java中ArrayList源碼解析】

Java中ArrayList源碼解析

前言

集合是Java開發人員必不可少的一個工具,而ArrayList是Java集合中的最為常用的一種,瞭解其底層原理能讓

我們更好的去使用它。本文將通過對於ArrayList類中源代碼的分析來逐步揭開其底層原理的神秘面紗 。

1. ArrayList的本質

我們從ArrayList類源碼中找到一個非常重要的成員變量:

transient Object[] elementData;

elementData翻譯過來可以理解為:元素數據,其實這個Object類型的數組就是最終ArrayList存放數據的容

器,由此可知,其實ArrayList的底層就是一個Object數組,而我們對集合元素的操作,本質上就是操作的這

個數組。

眾所周知,集合其實本質上就是一個容器,而ArrayList其實就是眾多容器的一種,其實數組也是一種容器,

為什麼程序員會更偏愛集合呢,最重要的原因就是:集合的長度是可以隨著元素的增刪而產生變化的,而數

組的長度一旦數組初始化完畢就已經固定了。我們來看一段代碼:

public class Test {
public static void main(String[] args) {
ArrayList<string> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 當集合添加四個元素後,集合的長度就是4
System.out.println("集合的長度為:" + list.size()); // 集合的長度為:4
// 當集合再次添加一個元素,集合的長度就已經變成了5
list.add("e");
System.out.println("集合的長度為:" + list.size()); // 集合的長度為:5
System.out.println("=================");
// 對於數組來說,一旦初始化完畢,數組的長度就已經固定了,不會隨著元素的賦值而改變
String[] strs = new String[5];
strs[0] = "a";
strs[1] = "b";
strs[2] = "c";
strs[3] = "d";
System.out.println("數組的長度為:"+strs.length); // 數組的長度為:5
strs[4] = "e";
System.out.println("數組的長度為:"+strs.length); // 數組的長度為:5
}
}
/<string>

以上代碼已經說明了使用集合的優點,長度可以動態的變化,免去了我們很多的煩惱。那麼現在問題來了,我們已經說過了,數組的長度是固定的,而ArrayList集合的底層是數組,長度卻是可以變化的,那麼這不是自相矛盾嗎?接下來我們通過集合"增刪改查"四個功能的源碼分析來揭曉答案。

2. ArrayList源碼解析之添加功能

2.1 添加功能源碼

我們都知道,元素添加的功能是add方法,話不多說,直接上源碼(我已經添加了中文註釋幫助閱讀):

public boolean add(E e) {
// modCount是用於統計元素修改次數的變量,最終會用於檢測併發修改異常,可參見我的另一篇文章:Java併發修改異常的源碼解析
modCount++;
// 添加元素的方法
add(e, elementData, size);
// 由於ArrayList允許添加重複元素,所以元素必定添加成功,返回值恆為true
return true;
}

private void add(E e, Object[] elementData, int s) {
// s為集合元素的個數,elementData.length為數組的長度,如果元素的個數已經等於集合的長度,那麼數組就需要擴容了
if (s == elementData.length)
// grow為擴容的方法,返回一個擴容後的新數組
elementData = grow();
// 將新添加進來的元素賦值給數組的對應位置。(size-1為最後一個元素的位置,那麼size就是第一個空的位置)
elementData = e;
// 添加完一個元素後,長度+1

size = s + 1;
}

private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity)); // copyOf是一個數組複製的方法,會將已有的元素都複製到一個更大的數組中,並返回新數組。
}

private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // newCapacity是新數組的容量,等於舊數組容量的1.5倍
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}

2.2 添加功能源碼分析

1. 第一個add方法中的第二行調用了第二個add方法,傳遞了三個參數,e表示要添加到集合中的元

素,elementData則是存儲元素的數組,size是集合元素的個數

2. 第二個add方法中,進行了一個集合容量的判斷:如果元素的個數已經等於集合的長度,那麼數組

就需要擴容了,擴容調用的是grow方法。

3. grow方法中返回了一個新的數組,由newCapacity方法中的代碼——int newCapacity =

oldCapacity + (oldCapacity >> 1)——可知,新數組的容量,等於舊數組容量的1.5倍。並將舊數組

的長度複製到的新數組中。

4. 擴容完畢後,回到第二個add方法中完成新元素的添加,並讓記錄元素個數的變量size值+1

以下是添加功能流程圖:

【成都校區*精品*Java中ArrayList源碼解析】

2.3 添加功能總結

到目前為止我們已經分析完了ArrayList添加元素的源代碼,我們可以發現,原來ArrayList集合底層存儲元素

的數組和我們使用的數組相比並沒有長度可變的"特異功能",實際是通過創建新數組擴容來達到使得集合長度

動態變化的目的,並且通過size值的變化來記錄元素個數。

接下來要分析的刪除方法相信你心中也有底了。

3. ArrayList源碼解析之刪除功能

3.1 刪除功能源碼

刪除功能我們分析remove功能,上源碼:

public boolean remove(Object o) {
// 如果要刪除的元素值為null,則遍歷數組查找null第一次出現的位置
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 調用方法刪除這個數據
fastRemove(index);
return true;
}
} else {
// 如果要刪除的元素不為null,則遍歷數組查找元素第一次出現的位置,並將元素刪除

for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
// 如果沒有找到需要被刪除的元素,則返回false,表示刪除失敗。
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
}

3.2 刪除功能源碼分析

1. remove方法中先對元素進行了為null的判斷,這樣做的好處是避免傳遞進來null造成元素內容比較時的空指針異常。

2. 無論是否為null,進行的操作都比較類似:先遍歷所有元素,查找要刪除的元素第一次出現的索引位置,如果找到了,則調用方法快速刪除,並返回true表示刪除成功

3. fastRemove方法中,對元素的刪除,本質上就是把要刪除元素位置後的所有元素往前移動一位,將要被刪除的元素覆蓋掉

4. 如果遍歷結束後還沒找到要刪除的元素,則表示要刪除的元素在集合中不存在,所以在remove方法的最後返回false,表示刪除失敗。

以下是刪除功能流程圖:

【成都校區*精品*Java中ArrayList源碼解析】

3.3 刪除功能總結

刪除功能源碼已經分析完了,ArrayList的刪除分為兩步:1. 找到被刪除的元素。2. 把要刪除元素用後續的元素覆蓋掉。當然,如果要刪除的元素是最後一個元素,則會把最後一個元素值設置為null,達到刪除元素的效果。

接下來要分析的修改和獲取功能就非常簡單了。

4. ArrayList源碼解析之修改功能和獲取功能

4.1 修改功能源碼

修改功能方法名為set,接收一個被修改元素的索引和新的元素,上源碼:

public E set(int index, E element) {
// 檢查是否索引越界,如果越界,則拋出異常
Objects.checkIndex(index, size);
// 先把被修改的元素用一個變量存儲起來
E oldValue = elementData(index);
// 將新的值賦值給被修改元素的位置
elementData[index] = element;
// 返回被修改的元素
return oldValue;
}

4.2 修改功能源碼分析

1. 由於修改元素需要指定被修改元素的索引位置,所以第一步先用checkIndex方法檢查索引是否超出正常索引範圍。

2. 把要被修改的元素先用一個變量存儲起來,以便於最後返回。

3. 修改元素就是把對應位置元素賦值為新的值

4. 最後返回被修改的值。

4.3 獲取功能源碼

獲取集合元素的功能為get,需要傳遞一個索引值,返回對應索引位置的元素,源碼如下:

public E get(int index) {
// 檢查index是否索引越界
Objects.checkIndex(index, size);
// 返回index所在的元素值
return elementData(index);
}

4.5 獲取功能源碼分析

1. 先檢查要獲取元素的索引是否越界,如果越界則會拋出索引越界異常

2. 將數組中對應索引位置的元素返回

4.6 獲取和修改功能總結

由於ArrayList是用一個數組來存儲元素的,所以獲取功能和修改功能都非常簡單。而在源碼中,這兩個功能第一步都對索引值進行了正確性判斷,這提醒我們以後的開發中需要更全面的考慮各種情況,保證代碼的健壯性。

5. 總結

那麼到目前為止,我們已經通過對ArrayList最主要的"增刪查改"四個功能源碼的分析,完成了本文對於ArrayList的介紹。我們可以做出以下總結:

數組本身是不可變的,ArrayList通過新數組的創建等操作,巧妙的完成了容器長度的變化。一些常見的異常,比如空指針異常和索引越界異常,ArrayList的功能都進行了比較全面的健壯性判斷,值得我們學習。

以上就是本文的全部內容,謝謝觀看。

【成都校區*精品*Java中ArrayList源碼解析】


分享到:


相關文章: