(轉載請附上鍊接: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,這個會有意思一點。