小豹子帶你看源碼:ArrayList

世界上最牛的 Java 代碼去哪找?固然是 JDK 咯~計劃學習一下常見容器的源碼。 我會把我以爲比較有意思或者玄學的地方更新到這裏。java

如下 JDK 源碼及 Javadoc 均從 java version "1.8.0_131" 版本實現中摘錄或翻譯 java.util.ArrayList算法


首先,開頭就頗有意思,聲明瞭兩個空數組:編程

116 行 - 126 行數組

/** * Shared empty array instance used for empty instances. */
private static final Object[] EMPTY_ELEMENTDATA = {};

/** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
複製代碼

有什麼差異?註釋上告訴咱們,EMPTY_ELEMENTDATA 用於空數組實例,而 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 用於默認的空數組實例,他們的區別在因而否能獲知首次添加元素時對數組的擴充量。這樣看咱們也是一頭霧水,不如咱們看一下他是怎樣應用的:bash

143行-166行多線程

/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

/** * Constructs an empty list with an initial capacity of ten. */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
複製代碼

咱們看,當咱們使用 new ArrayList() 建立 ArrayList 實例時,elementData 被賦值爲 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而若是咱們這樣建立實例 new ArrayList(0)elementData 將會被賦值爲 EMPTY_ELEMENTDATA。這看似沒什麼區別,咱們再看一下擴容數組的函數。併發

222 行 - 228 行ide

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
複製代碼

這個函數中對 elementData 的引用進行判斷,若是引用是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 則會在 DEFAULT_CAPACITY(值爲10)minCapacity 中選擇最大值爲擴容後的長度。這裏至關於用 elementData 的引用值作一個標記:若是咱們使用 new ArrayList() 建立實例,則 ArrayList 在首次添加元素時會將數組擴容至 DEFAULT_CAPACITY 若是咱們使用 new ArrayList(0) 建立實例則會按照 newCapacity = oldCapacity + (oldCapacity >> 1); (255行) 規則擴充實例。
那麼,爲何這樣作。咱們想,咱們使用空構造器和 new ArrayList(0) 建立實例的應用場景是不同的,前者是咱們沒法預估列表中將會有多少元素,後者是咱們預估元素個數會不多。所以ArrayList對此作了區別,使用不一樣的擴容算法。
然而令我驚訝的是,經過引用判斷來區別用戶行爲的這種方式,這是我想不到的,若是是我,我必定會再設置一個標誌變量。函數


238 行 - 244 行高併發

/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製代碼

這個變量顯然是數組的最大長度,可是爲何要 Integer.MAX_VALUE - 8 呢,註釋給了咱們答案:一些 VM 會在數組頭部儲存頭數據,試圖嘗試建立一個比 Integer.MAX_VALUE - 8 大的數組可能會產生 OOM 異常。


246 行 - 262 行

/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
複製代碼

此處是擴容數組的核心代碼,其計算新數組的長度採用 oldCapacity + (oldCapacity >> 1),新數組大概是原數組長度的 1.5 倍,使用位運算計算速度會比較快。
然而我想說的並非這個。而是這句話 if (newCapacity - minCapacity < 0)。在 ArrayList 類代碼中,隨處可見相似 a-b<0 的判斷,那麼爲何不用 a<b 作判斷呢?
下面是我寫的測試代碼:

int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
int newCap = 65421;
System.out.println(newCap > MAX_ARRAY_SIZE);
System.out.println(newCap - MAX_ARRAY_SIZE > 0);
複製代碼

這段代碼的輸出是 false false,咱們能夠看到,兩種寫法在正常狀況下是等效的。可是咱們考慮一下若是 newCap 溢出呢?咱們令 newCap = Integer.MAX_VALUE + 654321 輸出結果就變成 了false true。 這是爲何?

newCap:                  -2147418228
                          10000000000000001111111110001100
MAX_ARRAY_SIZE:          2147483639
                          01111111111111111111111111110111
MAX_ARRAY_SIZE*-1:       -2147483639
                          10000000000000000000000000001001
newCap - MAX_ARRAY_SIZE: 65429
                          00000000000000001111111110010101
複製代碼

咱們看,newCap 因爲溢出,進位覆蓋了符號位,所以 newCap 爲負,咱們將 newCap - MAX_ARRAY_SIZE 當作 newCap + MAX_ARRAY_SIZE*-1,加和時兩符號位相加又變成了 0(兩個大負數相加又一次溢出),結果爲正,而此次溢出偏偏是咱們想要的,獲得了正確的結果。


649 行 - 675 行

/** * The number of times this list has been <i>structurally modified</i>. * Structural modifications are those that change the size of the * list, or otherwise perturb it in such a fashion that iterations in * progress may yield incorrect results. * * <p>This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * <i>fail-fast</i> behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * * <p><b>Use of this field by subclasses is optional.</b> If a subclass * wishes to provide fail-fast iterators (and list iterators), then it * merely has to increment this field in its {@code add(int, E)} and * {@code remove(int)} methods (and any other methods that it overrides * that result in structural modifications to the list). A single call to * {@code add(int, E)} or {@code remove(int)} must add no more than * one to this field, or the iterators (and list iterators) will throw * bogus {@code ConcurrentModificationExceptions}. If an implementation * does not wish to provide fail-fast iterators, this field may be * ignored. */
protected transient int modCount = 0;
複製代碼

這個變量頗有意思,它是用來標記數組結構的改變。每一次數組發生結構改變(好比說增與刪)這個變量都會自增。當 List 進行遍歷的時候,遍歷的先後會檢查這個遍歷是否被改變,若是有改變則將拋出異常。 這種設計體現了 fail-fast 思想,這是一種編程哲學。儘量的拋出異常而不是武斷的處理一個可能會形成問題的異常。這種思想在不少應用場景的開發(尤爲是多線程高併發)都起着重要的指導做用。ErLang 就很提倡這種作法。

後記

看完 ArrayList 的源碼,我內心有這樣的感覺:嚴謹,高效,還有不少我以前不知道的操做。本身的代碼和大牛的代碼差距仍是很大的。看完這些我也不知道我能吸取多少……慢慢來吧。

相關文章
相關標籤/搜索