在上兩節文章中分別介紹了ArrayList的整體架構和初始化原理(需要查看的請前往《 》,《 》),在本節將ArrayList中我們最常用到的方法源碼進行分享,讓大家知道在平時我們使用的ArrayList基本方法底層是怎樣實現的。
1.新增、擴容實現
1.1 新增
新增就是往數組中添加元素。
- 新增方法add(E e),源碼如下:
add()是平時開發中最常用的方法之一,add(E e)方法是在已有元素數組的末尾附加新增的元素,從源碼中可以看出,新增分為兩步:
<strong>第一步:判斷是否需要擴容,如果需要,則先擴容(源碼:ensureCapacityInternal(size + 1));
<strong>第二步:直接賦值(elementData[size++] = e);
- 新增方法add(int index, E element),源碼如下:
該方法是ArrayList裡面的重載方法,用法就是在數組指定的下表插入元素,此方法新增分為下面幾步:
<strong>第一步:rangeCheckForAdd(index);檢測下表是否越界,使用如下方式判斷。
可以看出如果傳入的index大於或者小於0,直接拋出IndexOutOfBoundsException異常。
<strong>第二步:ensureCapacityInternal(size + 1);判斷是否需要擴容,關於擴容方法,下面會單獨講解。
<strong>第三步:System.arraycopy(elementData, index, elementData, index + 1, size - index);調用Java本地方法,進行數組拷貝。
arraycopy()方法是JVM提供的數組拷貝實現,使用該方法的目的就是為了提高性能。下面對參數做一個簡單的解釋:
- src 被拷貝的數組
- srcPos 從數組那裡開始
- dest 目標數組
- destPos 從目標數組那個索引位置開始拷貝
- length 拷貝的長度
<strong>第四步:直接賦值
1.2 擴容
擴容就是當期望的最小容量大於目前數組的長度時,進行擴容操作。源碼如下:
- 如果在構造數組時,給定了初始化大小,不執行if()操作;
- ensureExplicitCapacity(minCapacity);確保容量夠用;
在這段源碼中詳解講解一下ensureExplicitCapacity(int minCapacity)方法中的modCount:
- 這裡使用modCount的目的是為了標識線程安全,意思就是ArrayList只能在單線程環境下使用,在多線程情況會出現多線程併發安全問題,在之前章節就說過,modCount是為了記錄ArrayList被修改的次數,如果多個線程修改該值,就會拋出ConcurrentModificationException異常,這就是多線程中的“failFast機制”,該機制在很多類中都有所使用,比如HashMap。
- 然後判斷期望的最小容量大於目前數組的長度,就執行擴容。
以上兩個方法是擴容前的判斷操作。
源碼中的MAX_ARRAY_SIZE是ArrayList中數組的最大值,如果超過這個值,JVM就不會給ArrayList分配空間了,可能拋出內存溢出。
- oldCapacity >> 1 是把 oldCapacity 除以 2
- 如果擴容後的值 < 期望值,那麼擴容後的值就等於期望值
- 如果擴容後的值 > jvm 所能分配的數組的最大值,那麼就用 Integer-8 的最大值
- elementData = Arrays.copyOf(elementData, newCapacity);通過複製進行擴容
新增、擴容總結:
通過上面源碼我們需要注意以下幾點:
- ArrayList擴容規則並不是翻倍,而是在原來容量大小的基礎上擴大原來容量大小的一半,也就是說擴容後的容量是原來容量的1.5倍(源碼體現就是oldCapacity >> 1);
- 新增時並沒有對值進行嚴格的校驗操作,所以ArrayList中是允許出現null值的;
- ArrayList數組的最大值是Integer.MAX_VALUE-8,超過這個值,Java虛擬機就不會給ArrayList分配內存空間了,也有可能出現內存溢出的危險,所以在使用的時候需要格外注意;
- ArrayList只有作為共享變量時才會有線程安全問題,如果ArrayList只是方法內的局部變量,是沒有線程安全問題的;
- ArrayList會有線程安全問題是因為:ArrayList中size、modConut、elementData在進行操作時沒有加鎖。
閱讀更多 編程之藝術 的文章