天天看點

ArrayList初見

ArrayList是java集合架構中比較常見的一員,也是了解java架構的一個比較好的切入點。基于java1.8,ArrayList的繼承類圖如下:

ArrayList初見

ArrayList是java基于數組實作的非線程安全的連結清單資料結構,允許元素為null。内部維護了一個數組來存儲資料并在上面進行操作。下面分别描述一下ArrayList的構造和常用的方法。

成員變量

ArrayList初見
ArrayList初見

上圖是ArrayList中的全局變量和常量,DEFAULT_CAPACITY是内部數組預設初次加載的長度,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是兩個靜态常量,執行個體化ArrayList對象的時候會根據不同的情況可能将elementData(即内部數組)指向他們兩個其中的一個(如果初始元素個數為0)。elementData就是内部數組,資料都存儲在這裡。size是連結清單中元素的個數,MAX_ARRAY_SIZE是連結清單能夠存儲的最多的元素的個數。

構造函數

ArrayList初見

如上圖所示,ArrayList的構造函數有三個。他們的參數分别為沒有、初始數組長度、集合類。接下來分别分析一下。

沒有參數的構造函數很簡單,直接将内部數組elementData指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

有一個int類型參數的構造函數也比較簡單,如果參數為0,則把elementData指向EMPTY_ELEMENTDATA。如果參數大于0,則建立一個大小為參數的對象數組并讓elementData指向它。否則抛出異常。

以一個集合類型作為參數的構造函數也比較簡單。首先直接将toArray方法的傳回值指派給elementData,然後根據數組的長度是否為零來決定是否把elementData重新指向EMPTY_ELEMENTDATA。此外,如果數組長度大于0,還要判斷一下數組的類型是否是對象數組,必要時會通過複制的方式進行轉換。如上圖中所示。

以上就是ArrayList的全局變量和構造函數。也許你會問EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的差別。目前看來,他們的确是沒有差別的。當執行某些方法的時候,他們的差別才會顯現出來。

CRUD

作為連結清單的實作,我們先來看一下連結清單的CRUD方法。

ArrayList的add方法有兩個,分别是add(E)和add(index, E)。

add(E)

先上add(E)的圖

ArrayList初見

第一行暫且不考慮,可以看到代碼很簡單。就是在數組尾部添加一個元素,然後傳回true。

但是在構造函數那裡可以看到,ArrayList對象的elementData變量很有可能指向靜态數組。那麼在這裡會将元素添加到靜态數組當中去嗎?顯然不會。奧秘就是第一行代碼調用的方法中,它會為數組擴容。上圖。

ArrayList初見
ArrayList初見
ArrayList初見

大緻說一下它的具體流程,

首先判斷内部數組指向的是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。如果是的話,取參數與預設值(10)的最大值繼續向下傳遞,否則傳遞原參數。

第二個方法中判斷參數是否大于數組的容量。如果是的話,執行grow方法,否則什麼都不執行。

grow方法比較複雜,先擷取擴容後的數組的長度和參數中的最大值。如果這個最大值大于常量MAX_ARRAY_SIZE,會執行hugeCapacity方法。在這個方法中根據參數和MAX_ARRAY_SIZE的大小比較傳回不同的值并指派給newCapacity,也就是最後一行代碼中用到的值。這裡考慮的是int類型邊界的問題,可以暫時不考慮。隻需要看最後一行代碼即可。Arrays.copyOf方法常用于擴容數組,在這裡它将複制原數組到一個長度為newCapacity的新數組中并傳回給elementData。

回到add(E)方法,可以看到除了數組長度方面的代碼(擴容、防止越界),添加元素的部分代碼很少,并且在正常情況下恒定傳回true。

add(index,E)

上圖。

ArrayList初見
ArrayList初見

注釋中寫的比較明白了,在特定的位置插入特定的元素,并将該位置及其右邊的元素整體向右移動。

第一行代碼調用的方法如圖所示,隻是判斷index參數不能越界。

第二行代碼調用的方法和add(E)方法中一緻,不再贅述了。

然後就執行了System.arraycopy方法(Arrays.copyOf方法内部也調用了這個方法,native方法)将目前位置及其右邊的元素整體向右移動。

然後再把參數元素放到目前位置。

最後自增一下size變量。

注意這個方法沒有傳回值。

get(index)

先上圖

ArrayList初見
ArrayList初見

方法很簡單,先判斷一下參數的範圍,然後傳回參數指定位置的元素。這也是ArrayList的優勢,查詢速度很多。

set(index,E)

上圖

ArrayList初見

正如注釋所言,用特定的元素(參數)取代連結清單特定位置的元素。并傳回被取代的元素。代碼非常直白,不多說了。

remove(index)

上圖

ArrayList初見

沒有什麼比注釋說的更清楚的了:删除連結清單中特定位置的元素并将其右邊所有的元素左移。

第一行依然是檢查index的範圍。

然後自增modCount變量。

取出指定位置的元素,并将此元素右邊所有的元素左移一個元素的位置。

将原數組最右邊的元素廢棄并将size變量自減。(因為涉及到ArrayList元素的移動都是通過複制來實作的,是以這裡需要處理一下以達到整體移動的效果)

傳回取出的指定位置的元素。

remove(object)

上圖

ArrayList初見
ArrayList初見

如注釋所言,删除連結清單中第一個出現的目标元素,如果不存在這樣的元素,那麼連結清單将不會發生變化。如果删除成功将傳回true,否則傳回false。

看一下方法體内的代碼,如果參數為null,那麼将周遊連結清單尋找null,找到之後執行fastRemove(index)方法。否則依然周遊連結清單尋找特定元素,找到之後執行fastRemove(index)方法。注意這裡用的是equals。邏輯并不複雜,簡單來講就是找到目标的位置,并根據位置進行删除。

再來看fastRemove(index)方法,和remove(index)方法差不多。不多說了。

總結

以上簡單介紹了ArrayList類的一些基礎内容。包括成員變量、構造函數以及對單個元素的操作。實際上ArrayList中的方法遠不止于此,很多都值得拿出來單獨分析一番。為了篇幅不至于過長,就先到此為止吧。