ArrayList是java集合架構中比較常見的一員,也是了解java架構的一個比較好的切入點。基于java1.8,ArrayList的繼承類圖如下:
ArrayList是java基于數組實作的非線程安全的連結清單資料結構,允許元素為null。内部維護了一個數組來存儲資料并在上面進行操作。下面分别描述一下ArrayList的構造和常用的方法。
成員變量
上圖是ArrayList中的全局變量和常量,DEFAULT_CAPACITY是内部數組預設初次加載的長度,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是兩個靜态常量,執行個體化ArrayList對象的時候會根據不同的情況可能将elementData(即内部數組)指向他們兩個其中的一個(如果初始元素個數為0)。elementData就是内部數組,資料都存儲在這裡。size是連結清單中元素的個數,MAX_ARRAY_SIZE是連結清單能夠存儲的最多的元素的個數。
構造函數
如上圖所示,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)的圖
第一行暫且不考慮,可以看到代碼很簡單。就是在數組尾部添加一個元素,然後傳回true。
但是在構造函數那裡可以看到,ArrayList對象的elementData變量很有可能指向靜态數組。那麼在這裡會将元素添加到靜态數組當中去嗎?顯然不會。奧秘就是第一行代碼調用的方法中,它會為數組擴容。上圖。
大緻說一下它的具體流程,
首先判斷内部數組指向的是不是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)
上圖。
注釋中寫的比較明白了,在特定的位置插入特定的元素,并将該位置及其右邊的元素整體向右移動。
第一行代碼調用的方法如圖所示,隻是判斷index參數不能越界。
第二行代碼調用的方法和add(E)方法中一緻,不再贅述了。
然後就執行了System.arraycopy方法(Arrays.copyOf方法内部也調用了這個方法,native方法)将目前位置及其右邊的元素整體向右移動。
然後再把參數元素放到目前位置。
最後自增一下size變量。
注意這個方法沒有傳回值。
get(index)
先上圖
方法很簡單,先判斷一下參數的範圍,然後傳回參數指定位置的元素。這也是ArrayList的優勢,查詢速度很多。
set(index,E)
上圖
正如注釋所言,用特定的元素(參數)取代連結清單特定位置的元素。并傳回被取代的元素。代碼非常直白,不多說了。
remove(index)
上圖
沒有什麼比注釋說的更清楚的了:删除連結清單中特定位置的元素并将其右邊所有的元素左移。
第一行依然是檢查index的範圍。
然後自增modCount變量。
取出指定位置的元素,并将此元素右邊所有的元素左移一個元素的位置。
将原數組最右邊的元素廢棄并将size變量自減。(因為涉及到ArrayList元素的移動都是通過複制來實作的,是以這裡需要處理一下以達到整體移動的效果)
傳回取出的指定位置的元素。
remove(object)
上圖
如注釋所言,删除連結清單中第一個出現的目标元素,如果不存在這樣的元素,那麼連結清單将不會發生變化。如果删除成功将傳回true,否則傳回false。
看一下方法體内的代碼,如果參數為null,那麼将周遊連結清單尋找null,找到之後執行fastRemove(index)方法。否則依然周遊連結清單尋找特定元素,找到之後執行fastRemove(index)方法。注意這裡用的是equals。邏輯并不複雜,簡單來講就是找到目标的位置,并根據位置進行删除。
再來看fastRemove(index)方法,和remove(index)方法差不多。不多說了。
總結
以上簡單介紹了ArrayList類的一些基礎内容。包括成員變量、構造函數以及對單個元素的操作。實際上ArrayList中的方法遠不止于此,很多都值得拿出來單獨分析一番。為了篇幅不至于過長,就先到此為止吧。