史上最詳細的Java集合類ArrayList源代碼逐行深入解讀

(轉載請附上鍊接:https://blog.csdn.net/brucexiajun/article/details/101209837

前言:ArrayList是Java集合類中非常常見的一個類,而且比較基本,不會太難,源代碼1500行左右,非常適合新手開始練習源代碼的閱讀能力。

本文將會盡可能詳細的剖析ArrayList類的源代碼,文章會陸續更新。

一 註釋

我們先從註釋開始

ArrayList是對於List接口的大小可變的數組實現,它實現了所有可選的List操作,而且支持所有的數據,包括null。除了實現了List的接口,它還能夠操作類的內部用來存儲數據的數組大小的方法(這個類和Vector非常像,除了它不是同步的這一點外)

(下面的部分我不再對註釋進行截屏,只給出翻譯結果)

方法size,isEmpty,get,set,iterator,listIterator運行時間是固定的,add操作運行時間是攤銷的,就是說,添加一個元素需要O(n)的時間,所有其他操作的運行時間都是線性的(粗略的說)。相比LinkedList的實現來說,常數因子要小。

每一個ArrayList的對象都有一個容量,指的是用來存儲list中的元素的數組的大小,它的大小總是至少爲list的大小。隨着元素被一個個的添加到ArrayList中,它的容量會自動增長。除了添加一個元素會導致攤銷的時間成本外,容量增加的細節沒有具體的規定。

在使用ensureCapacity操作向它添加大量元素之前,一個應用可以增加ArrayList對象的容量。這樣可能會減少逐漸增加的重新分配過程。(這裏翻譯感覺有問題)

需要注意的是,這個實現(指ArrayList)不是同步的。如果多個線程同時使用同一個ArrayList的實例,而且其中至少有一個從結構上修改了它,它必須從外部被實現同步機制。(一個結構化的修改指的是一種操作,這種操作會添加或者刪除一個或者多個元素,或者顯式地修改支持數組的大小,而僅僅設置一個元素的值不是結構化的操作。)典型地,這種同步機制會採用同步一些object來實現,這些object封裝了list。

如果這樣的object不存在,list需要使用{@link Collections#synchronizedList Collections.synchronizedList}方法來包裹,而且最好是在構建的時候,從而防止可能的對list的非同步操作:

        List list = Collections.synchronizedList(new ArrayList(...));

該類的iterator()和listIterator()方法返回的迭代器是快速失敗的:在迭代器被創建出來之後的任何時間,如果list的結構被修改了,除非是通過迭代器自己的方法如ListIterator.remove()或者ListIterator.add(),迭代器會拋出ConcurrentModificationException異常。因此,在同步修改的情況下,迭代器會快速失敗,而不是冒着失控的風險:不確定的行爲在未來某一個不確定的時間。

注意到迭代器的快速失敗行爲不能保證如描述的那樣,就是說,在不同步的併發修改情況下不可能百分之百保證快速失敗。快速失敗的迭代器會盡力拋出ConcurrentModificationException異常。因此,編寫一個依賴這個異常的的程序是不可取的:迭代器的快速失敗行爲只能被用來檢測bug。

(這個類的註釋就這麼多,讀完之後基本上對類有了認識,下面挑幾個方法詳細解讀一下)

 

二 開始定義的一些變量和常量

我們按照順序來閱讀代碼

一開始定義了一些變量,常量:

默認的容量是10

爲空的ArrayList準備的空的數組實例

爲默認大小的空的ArrayList準備的空的數組實例

elementData就是存放ArrayList元素的地方,這個非常重要,它是一個數組,所以ArrayList內部是使用數組實現的。

transient意思是短暫的,在Java中表示它修改的變量不會被序列化(關於序列化自己去找文章,這裏不解釋了)。

size表示list對象裏面實際存儲了幾個元素

capacity(容量)表示可以存儲幾個元素

 

三 構造函數

接下來的內容是構造函數

該類有3個構造函數:

構造一個指定容量的對象,可以很明顯的看出,ArrayList本質上就是一個Object類的數組

第二個構造函數簡單略過,直接看第三個:

這是用一個Collection的對象構造ArrayList

c.toArray()

返回的是一個Object數組,含有Collection所有的元素,所以大部分情況下到這一句已經結束了。但是c.toArray()有時候會出錯,所以有了下面的選擇語句:

 elementData = Arrays.copyOf(elementData, size, Object[].class);

這句話的意思是:將elementData轉換爲長度是size,類型是Object[].class的數組,一般情況下返回的就是本身,除非size比本身長度要長,那麼剩下的空間就用null補齊。

下面我會挑一些核心的方法解釋。

 

四 indexOf()方法

indexOf的作用是返回對象o在list中第一次出現時的索引。

首先,區分一下o是不是null,因爲equals方法不支持null參數,然後從索引0處開始遍歷,找到了o就直接返回。最後,如果找不到,返回-1,表示list中不存在這個元素。

 

五 add()方法

核心是第一句,所以我們先來看一下ensureCapacityInternal()方法

看一下里層的calculateCapacity方法:

大部分情況下都是直接返回minCapacity,而這個minCapacity傳進來的就是size+1,也就是當前元素的個數加上1.

再來看一下外層的函數ensureExplicitCapacity:

關於這個modCount,在它初始化的地方有一段的註釋,我簡要翻譯一下:

modCount表示list已經被結構化修改的次數,主要用在iterator()和listIterator()方法中。

因爲add屬於結構化的修改,所以這裏modCount會加1

minCapacity是我們需要的長度(也就是添加一個元素之後元素的個數),而elementData.length是當前存儲元素的數組的長度,相減大於0表明數組需要擴充了,所以調用了grow()方法:

第二行,newCapacity等於本身加上本身右移一位(乘以2)

第三行,如果newCapacity還是小於minCapacity(實際需要的長度),直接賦值

第五行,如果newCapacity大於一個上限(0x7fffffff-8),則進行一個特殊處理(這裏我們不深入了,沒必要)

第七行,核心,copyOf就是新建一個數組,並把原數組的元素拷貝進去,新數組的長度是newCapacity。

總結一下add方法,如果需要的長度大於數組本身的長度,就擴充,擴充後的大小一般爲原來數組的長度加上它的一半,擴充之後將原來的元素拷貝進去就可以了。

 

六 remove(int index)方法

它的源代碼如下所示:

註釋的意思是:

刪除list中指定位置的元素,然後把後面的元素統統左移

下面逐行來看一下代碼:

第一句是檢查索引的範圍,超出範圍就會拋出IndexOutOfBoundException異常

第二句,記錄修改次數的參數加1,因爲這裏我們做了結構化的修改

第三句,取出這個位置的元素,因爲用數組存儲的,直接取出

第四句,計算需要移動的元素的個數

第五句,判斷是否有需要移動的元素,有的話調用System.arraycopy(一個native方法,用c或者c++實現的)來移動元素

第八句,將最後一個元素賦值爲空(因爲它左移了),提醒垃圾回收機制進行回收

最後返回這個位置的元素

所以其實這個remove也很簡單

 

好了,ArrayList的方法就介紹這麼多,因爲它的方法都比較簡單,沒有涉及到複雜的數據結構,後面我會出一篇文章介紹ConcurrentHashMap,這個會有意思一點。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章