ArrayList源碼解析之新增、擴容

在上兩節文章中分別介紹了ArrayList的整體架構和初始化原理(需要查看的請前往《 》,《 》),在本節將ArrayList中我們最常用到的方法源碼進行分享,讓大家知道在平時我們使用的ArrayList基本方法底層是怎樣實現的。

1.新增、擴容實現

1.1 新增

新增就是往數組中添加元素。

  • 新增方法add(E e),源碼如下:
ArrayList源碼解析之新增、擴容

圖1.1.1 ArrayList新增方法1

add()是平時開發中最常用的方法之一,add(E e)方法是在已有元素數組的末尾附加新增的元素,從源碼中可以看出,新增分為兩步:

<strong>第一步:判斷是否需要擴容,如果需要,則先擴容(源碼:ensureCapacityInternal(size + 1));

<strong>第二步:直接賦值(elementData[size++] = e);

  • 新增方法add(int index, E element),源碼如下:
ArrayList源碼解析之新增、擴容

圖1.1.2 ArrayList新增方法2

該方法是ArrayList裡面的重載方法,用法就是在數組指定的下表插入元素,此方法新增分為下面幾步:

<strong>第一步:rangeCheckForAdd(index);檢測下表是否越界,使用如下方式判斷。

ArrayList源碼解析之新增、擴容

判斷下表是否越界

可以看出如果傳入的index大於或者小於0,直接拋出IndexOutOfBoundsException異常。

<strong>第二步:ensureCapacityInternal(size + 1);判斷是否需要擴容,關於擴容方法,下面會單獨講解。

<strong>第三步:System.arraycopy(elementData, index, elementData, index + 1, size - index);調用Java本地方法,進行數組拷貝。

ArrayList源碼解析之新增、擴容

Java本地數組拷貝方法

arraycopy()方法是JVM提供的數組拷貝實現,使用該方法的目的就是為了提高性能。下面對參數做一個簡單的解釋:

  1. src 被拷貝的數組
  2. srcPos 從數組那裡開始
  3. dest 目標數組
  4. destPos 從目標數組那個索引位置開始拷貝
  5. length 拷貝的長度

<strong>第四步:直接賦值

1.2 擴容

擴容就是當期望的最小容量大於目前數組的長度時,進行擴容操作。源碼如下:

ArrayList源碼解析之新增、擴容

ArrayList數組擴容前判斷

  1. 如果在構造數組時,給定了初始化大小,不執行if()操作;
  2. ensureExplicitCapacity(minCapacity);確保容量夠用;

在這段源碼中詳解講解一下ensureExplicitCapacity(int minCapacity)方法中的modCount

  • 這裡使用modCount的目的是為了標識線程安全,意思就是ArrayList只能在單線程環境下使用,在多線程情況會出現多線程併發安全問題,在之前章節就說過,modCount是為了記錄ArrayList被修改的次數,如果多個線程修改該值,就會拋出ConcurrentModificationException異常,這就是多線程中的“failFast機制”,該機制在很多類中都有所使用,比如HashMap。
  • 然後判斷期望的最小容量大於目前數組的長度,就執行擴容。

以上兩個方法是擴容前的判斷操作。

ArrayList源碼解析之新增、擴容

ArrayList擴容

源碼中的MAX_ARRAY_SIZE是ArrayList中數組的最大值,如果超過這個值,JVM就不會給ArrayList分配空間了,可能拋出內存溢出。

  • oldCapacity >> 1 是把 oldCapacity 除以 2
  • 如果擴容後的值 < 期望值,那麼擴容後的值就等於期望值
  • 如果擴容後的值 > jvm 所能分配的數組的最大值,那麼就用 Integer-8 的最大值
  • elementData = Arrays.copyOf(elementData, newCapacity);通過複製進行擴容

新增、擴容總結:

通過上面源碼我們需要注意以下幾點:

  1. ArrayList擴容規則並不是翻倍,而是在原來容量大小的基礎上擴大原來容量大小的一半,也就是說擴容後的容量是原來容量的1.5倍(源碼體現就是oldCapacity >> 1);
  2. 新增時並沒有對值進行嚴格的校驗操作,所以ArrayList中是允許出現null值的;
  3. ArrayList數組的最大值是Integer.MAX_VALUE-8,超過這個值,Java虛擬機就不會給ArrayList分配內存空間了,也有可能出現內存溢出的危險,所以在使用的時候需要格外注意;
  4. ArrayList只有作為共享變量時才會有線程安全問題,如果ArrayList只是方法內的局部變量,是沒有線程安全問題的;
  5. ArrayList會有線程安全問題是因為:ArrayList中size、modConut、elementData在進行操作時沒有加鎖。


分享到:


相關文章: