Java集合源碼之ArrayList

1. 簡介

ArrayList可以說是我們最常用的一種集合了。

他的本質是一個數組,一個可以自動擴容的動態數組線程不安全允許元素爲null

由於數組的內存連續,可以根據下標以O(1)的時間讀寫元素,因此時間效率很高。

2. 內部屬性

我們先來看下ArrayList裏面有哪幾個屬性:

  • private static final long serialVersionUID = 8683452581122892189L;
    序列話UID。由於ArrayList實現了Serializable接口,爲了序列化和反序列化的方便,我們就手動爲他添加一個序列化UID。

  • private static final int DEFAULT_CAPACITY = 10;
    默認的容量。

  • private static final Object[] EMPTY_ELEMENTDATA = {};
    空數組。

  • private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    默認的空數組。

  • transient Object[] elementData;
    真正存放元素的數組。

  • private int size;
    當前元素個數。

3. 構造方法

// 傳入參數爲初始化容量時的構造方法
public ArrayList(int initialCapacity) {

    if (initialCapacity > 0) {
        // 如果傳入參數大於零,那就創建一個對應大小的數組
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 如果傳入參數等於0,那就直接把屬性中創建好的空數組複製
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 如果傳入參數小於0,那就拋異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 傳入參數爲空時的構造方法
public ArrayList() {
    // 將屬性中創建好的空數組複製過來
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 傳入參數爲數組時的構造方法
public ArrayList(Collection<? extends E> c) {
    // 先轉換爲數組
    elementData = c.toArray();
    // 因爲size代表ArrayList中元素個數,所以要把數組的長度賦過來
    // 如果個數不爲0進入此if
    if ((size = elementData.length) != 0) {
        //這裏是當c.toArray出錯,沒有返回Object[]時,利用Arrays.copyOf 來複制集合c中的元素到elementData數組中
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 如果個數爲0,那就把屬性中的空數組複製過來
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

4. 常用API

4.1 增

ArrayList # add(E e)

public boolean add(E e) {
    // ->> 分析4.1.1
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 將需要加入的元素加到最後面
    elementData[size++] = e;
    return true;
}

/**
 * 分析4.1.1 ensureCapacityInternal()
 */
private void ensureCapacityInternal(int minCapacity) {
    // DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個變量只有在通過無參構造的時候用到過
    // 也就是說,他判斷創建出來的這個數組,到底是不是無參構造方法創建出來的,如果是就找出DEFAULT_CAPACITY和minCapacity中較大的那個
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 但是按道理來說,add一個元素minCapacity肯定爲1,肯定小於DEFAULT_CAPACITY,爲什麼還要做一個判斷呢?
        // 但是你得注意,還有AddAll這個方法
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    
    // ->> 分析4.1.2
    ensureExplicitCapacity(minCapacity);
}

/**
 * 分析4.1.2 ensureExplicitCapacity()
 */
private void ensureExplicitCapacity(int minCapacity) {
    // modCount是AbstractList中的屬性,如果需要擴容,則會修改modCount
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        // ->> 分析4.1.3
        grow(minCapacity);
}

/**
 * 分析4.1.3 grow()
 * 作用:擴容
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 擴容爲原數組的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果還不夠,就直接用能容納的最小大小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
    // 如果新數組比MAX_ARRAY_SIZE還要大的話
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // ->> 分析4.1.4
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually clos2019e to size, so this is a win:
    // 生成新數組
    elementData = Arrays.copyOf(elementData, newCapacity);
}

/**
 * 分析4.1.4
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

ArrayList # add(E e)

// 按給定的位置添加指定元素
public void add(int index, E element) {
    // 如果給的位置超過了集合已存放的元素的個數或者小於0,就拋異常
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // ->> 分析4.1.1
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 將elementData從index處分開,給index空出位置
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 放入數據
    elementData[index] = element;
    size++;
}

ArrayList # addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {
    // 轉爲數組
    Object[] a = c.toArray();
    int numNew = a.length;
    // 擴容數組 ->> 分析4.1.1
    ensureCapacityInternal(size + numNew);  // Increments modCount
    // 將a數組的全部內容添加到elementData數組從size開始之後
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

ArrayList # addAll(int index, Collection<? extends E> c)

public boolean addAll(int index, Collection<? extends E> c) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // 轉爲數組
    Object[] a = c.toArray();
    int numNew = a.length;
    // 擴容 ->> 分析4.1.1
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    // 如果index小於size,也就是說需要在原數組的中間插入的haul,就需要先把原數組index之後的數據往後移動
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

總結

  • 無論是add還是addAll,都是先判斷是否越界,如果越界就擴容,然後再移動數組
  • 如果需要擴容,默認擴容原來的一般大小;如果還不夠,那就直接將目標的size作爲擴容後的大小
  • 在擴容成功後,會修改modCount

4.2 刪

ArrayList # remove(int index)

public E remove(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    // 修改modCount
    modCount++;
    // 將要刪除的內容先保存下來
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    // 判斷要刪除的數據是不是最後一位
    // 如果不是最後一位,還得先把後面的數據往前移動
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 清除數據,更改引用,讓GC去清理
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

ArrayList # remove(Object o)

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                // ->> 分析4.2.1
                fastRemove(index);
                return true;
            }
    } else {
        // 如果參數不爲空,那就遍歷整個數組集合
        for (int index = 0; index < size; index++)
            // 找到和參數相等的那一位,然後將該位移除
            if (o.equals(elementData[index])) {
                // ->> 分析4.2.1
                fastRemove(index);
                return true;
            }
    }
    return false;
}

/** 
 * 分析4.2.1:fastRemove()
 * 作用:基本上和remove(int index)一樣
 */
private void fastRemove(int index) {
    // 修改modCount
    modCount++;
    int numMoved = size - index - 1;
    // 判斷要刪除的數據是不是最後一位
    // 如果不是最後一位,還得先把後面的數據往前移動
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 清除數據,更改引用,讓GC去清理
    elementData[--size] = null; // clear to let GC do its work
}

ArrayList # removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    // ->> 分析4.2.2
    return batchRemove(c, false);
}

/** 
 * 分析4.2.2 batchRemove()
 */
private boolean batchRemove(Collection<?> c, boolean complement) {
    // 先賦值
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        // 快速保存兩個集合共有元素
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
            // 如果出現異常會導致r!=size,就把異常之後的數據全部覆蓋到數組裏面
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            // 將後面的元素全部置空,讓GC來回收
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

這塊我看的時候有點繞,但是一想通就好了。

其實這個地方是要把C集合中和原集合中共有的元素刪除,那我就只需要遍歷原數組,然後碰到和C集合相同的元素就直接放到前面去,然後等遍歷完成後,一次性把後面的全部置空。

ArrayList # retainAll(Collection<?> c)

public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    // ->> 分析4.2.2
    return batchRemove(c, true);
}

ArrayList # clear()

public void clear() {
    modCount++;

    // clear to let GC do its work
    // 直接遍歷每一位,然後把每一位都置空,讓GC去清理
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

總結

  • 所有的刪除操作都會修改modCount

4.3 改

public E set(int index, E element) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // 取出原來的元素
    E oldValue = (E) elementData[index];
    // 把需要更改的數據放進去
    elementData[index] = element;
    return oldValue;
}

沒啥好分析的

不需要修改modCount,相對高效

4.4 查

public E get(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    return (E) elementData[index];
}

沒啥好分析的

不需要修改modCount,相對高效

4.5 包括 contains() & indexOf()

public boolean contains(Object o) {
    // ->> 分析4.5.1
    return indexOf(o) >= 0;
}

/**
 * 分析4.5.1:indexOf()
 */
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

沒啥好分析的

4.6 判空 isEmpty()

public boolean isEmpty() {
    return size == 0;
}

沒啥好分析的

4.7 縮容 trimToSize()

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        // 如果size==0的時候,就直接把EMPTY_ELEMENTDATA複製過去
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

5. 迭代器 Iterator

5.1 創建迭代器

public Iterator<E> iterator() {
    // 構造Itr對象並返回
    return new Itr();
}

5.2 Itr屬性

// 限制,也就是數組的元素個數
protected int limit = ArrayList.this.size;

// 下一個元素的下標
int cursor;       // index of next element to return
// 上一次返回元素的下標
int lastRet = -1; // index of last element returned; -1 if no such
// 用於判斷集合是否修改過結構的標誌
int expectedModCount = modCount;

5.3 Itr # hasNext()

public boolean hasNext() {
    return cursor < limit;
}

不用多說

5.4 Itr # next()

public E next() {
    // 判斷是否修改過List的結構,如果修改了就拋異常
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    int i = cursor;
    // 如果越界了就拋異常
    if (i >= limit)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    // 再次判斷是否越界,在 我們這裏的操作時,有異步線程修改了List
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    // 標記加1
    cursor = i + 1;
    // 返回數據,並設置上一次的下標
    return (E) elementData[lastRet = i];
}

5.5 Itr # remove()

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

    try {
        // 調用ArrayList的remove方法移除數據
        ArrayList.this.remove(lastRet);
        // 更新一系列數據
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
        limit--;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

6. 總結

6.1 modCount到底是個什麼東西?

如果你看過很多集合源碼的話,你就會發現你會在很多地方都會碰到這個modCount,例如ArrayList、LinkedList、HashMap等等。modCount字面意思就是修改次數,那麼爲什麼需要紀錄這個修改次數呢?

你回想下,似乎用到modCount的地方,如ArrayList、LinkedList等等,他們都用一個共性——線程不安全。

而在你看了這幾個類的迭代器後就會發現,他們迭代器中一定有一個屬性(如我們上面的expectedModCount)初始化時的值就是modCount的值。

然後在迭代器的方法中,只要是遍歷這個集合的時候,都需要將兩個值進行對比,然後再移除元素的時候,都需要更新迭代器裏面的modCount變量。

這時,你需要了解下Fail-Fast機制

我們都知道這些集合是線程不安全的,如果在使用迭代器的過程中,有其他線程對集合進行了修改,那麼就會拋出ConcurrentModificationException異常,這就是Fail-Fast策略。而這個時候源碼中就通過modCount進行了操作。迭代器在創建時,會創建一個變量等於當時的modCount,如果在迭代過程中,集合發生了變化,modCount就是++。這時迭代器中的變量的值和modCount不相等了,那就拋異常。

所以,遍歷線程不安全的集合時,儘量使用迭代器

解決辦法:

  • 在遍歷過程中所有涉及到改變 modCount 值得地方全部加上 synchronized 或者直接使用 Collections.synchronizedList,這樣就可以解決。但是不推薦,因爲增刪造成的同步鎖可能會阻塞遍歷操作。
  • 使用 CopyOnWriteArrayList 來替換 ArrayList。推薦使用該方案。關於CopyOnWriteArrayList的內容此處不再過多的去將,想了解的同學可以百度或者谷歌。

6.2 ArrayList和Vector的區別

  • ArrayList線程不安全,Vector線程安全
  • 擴容的時候ArrayList默認擴容1.5倍,Vector默認擴容1倍
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章