Java集合 - ArrayList

繼承關系

ArrayList繼承關系圖

1. ArrayList概述

  • ArrayList是List接口的可變數組的實現。實現了所有可選列表操作,并允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。
  • 每個 ArrayList 實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它總是至少等于列表的大小。隨著向 ArrayList 中不斷添加元素,其容量也自動增長。自動增長會帶來數據向新數組的重新拷貝,因此,如果可預知數據量的多少,可在構造 ArrayList 時指定其容量。在添加大量元素前,應用程序也可以使用 ensureCapacity操作來增加 ArrayList實例的容量,這可以減少遞增式再分配的數量。

注意:此實現不是同步的。如果多個線程同時訪問一個 ArrayList 實例,而其中至少一個線程從結構上修改了列表,那么它必須保持外部同步。相對比的,Vector 是線程安全的,其中涉及線程安全的方法皆被同步操作了。

2. ArrayList實現

  • 對于 ArrayList 而言,它實現 List 接口、底層使用數組保存所有元素。其操作基本上是對數組的操作。下面我們來分析 ArrayList 的Java源代碼:

(1)底層實現:數組

 /**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

(2)構造器

1)構造初始容量為10的空集合
    /**
 * Constructs an empty list with an initial capacity of ten. 
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2)構造指定初始容量為initialCapacity的空集合
    /**
 * 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);
    }
}    
3)構造一個包含指定集合元素的列表,其順序由集合的迭代器返回
/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

(3)存儲

  • ArrayList 提供了 set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c) 這些添加元素的方法
1)set(int index,E element):用指定的元素代替此列表中指定位置的元素,并返回之前位于該位置的元素
    /**
 * Replaces the element at the specified position in this list with
 * the specified element.
 *
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
2)add(E e):將指定的元素添加到此列表的尾部
    /**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

add(E e) 的實現原理:

  • 第一步:檢查列表是否需要擴容
    • 容量足夠,直接添加
    • 容量不足,擴容
  • 第二步:在列表尾部插入元素
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 跟默認容量10比較,得到最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
// 比較看是否需要擴容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

擴容的方法grow(int minCapacity):

    /**
 * 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);  // 擴容1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;  // 如果擴容還不滿足要求,則將容量直接擴為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);
}    

擴容原理:第一次擴容為原來的1.5倍,如果第一次擴容后還沒有達到要求,直接擴容為minCapacity。

3)add(int index, E element):將指定元素插入此列表指定位置
    /**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);
    // 如果當前位置有元素,則向右移動當前位于該位置的元素以及所有后續元素(將其索引加1)
    // 檢查容量,如果數組長度不足,將其擴容,擴容原理同上
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 將elementData中從index位置、長度為size-index的元素,拷貝到從下標為index+1
    // 位置開始的新的elementData數組中,即將當前位于該位置以及所有后續元素向后移動一個位置
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}
4)addAll(Collection<? extends E> c):按照指定集合的迭代器返回的順序,將指定集合中的所有元素追加到此列表的末尾
    /**
 * Appends all of the elements in the specified collection to the end of
 * this list, in the order that they are returned by the
 * specified collection's Iterator.  The behavior of this operation is
 * undefined if the specified collection is modified while the operation
 * is in progress.  (This implies that the behavior of this call is
 * undefined if the specified collection is this list, and this
 * list is nonempty.)
 *
 * @param c collection containing elements to be added to this list
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
5)addAll(int index, Collection<? extends E> c):從指定位置開始,將指定collection中的所有元素添加到此列表的尾部
    /**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in the list in the order that they are returned by the
 * specified collection's iterator.
 *
 * @param index index at which to insert the first element from the
 *              specified collection
 * @param c collection containing elements to be added to this list
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

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

(4)讀取元素:獲取列表中某個位置的元素

    /**
 * Returns the element at the specified position in this list.
 *
 * @param  index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

(5)刪除:刪除指定位置元素

    /**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);
    // 左移元素個數
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 元素左移
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
     // 空閑位置設置為null,讓GC回收
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

(6)容量調整

  • 從上面介紹的向 ArrayList 中存儲元素的代碼中,我們看到,每當向數組中添加元素時,都要去檢查添加后元素的個數是否會超出當前數組的長度,如果超出,數組將會進行擴容,以滿足添加數據的需求。數組擴容通過一個公開的方法 ensureCapacity(int minCapacity)來實現。在實際添加大量元素前,我也可以使用 ensureCapacity來手動增加 ArrayList 實例的容量,以減少遞增式再分配的數量
    擴容核心代碼:

     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);
    }
    
  • 數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中,每次數組容量的增長大約是其原容量的 1.5 倍【oldCapacity + (oldCapacity >> 1),等于oldCapacity +(oldCapacity /2)】。這種操作的代價是很高的,因此在實際使用時,我們應該盡量避免數組容量的擴張。當我們可預知要保存的元素的多少時,要在構造 ArrayList 實例時,就指定其容量,以避免數組擴容的發生。或者根據實際需求,通過調用 ensureCapacity 方法來手動增加 ArrayList 實例的容量。

  • ArrayList 還給我們提供了將底層數組的容量調整為當前列表保存的實際元素的大小的功能。它可以通過 trimToSize 方法來實現。

      /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
      modCount++;
      if (size < elementData.length) {
          elementData = (size == 0)
            ? EMPTY_ELEMENTDATA
            : Arrays.copyOf(elementData, size);
      }
    }
    

3.Fail-Fast 機制

(1)fail-fast策略:

  • 我們知道 java.util.HashMap 不是線程安全的,因此如果在使用迭代器的過程中有其他線程修改了集合,那么將拋出 ConcurrentModificationException,這就是所謂 fail-fast策略。fail-fast 機制是 java 集合(Collection)中的一種錯誤機制。 當多個線程對同一個集合的內容進行操作時,就可能會產生 fail-fast 事件。

(2)舉例

  • 例如:當某一個線程 A 通過 iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那么線程 A 訪問集合時,就會拋出 ConcurrentModificationException 異常,產生 fail-fast 事件。
  • 這一策略在源碼中的實現是通過 modCount 域,modCount 顧名思義就是修改次數,對集合內容(HashMap、 ArrayList 中都有)的修改都將增加這個值,那么在迭代器初始化過程中會將這個值賦給迭代器的 expectedModCount。

(3)ArrayList

  • ArrayList 也采用了快速失敗的機制,通過記錄 modCount 參數來實現。在面對并發的修改時,迭代器很快就會完全失敗,而不是冒著在未來某個不確定時間發生任意不確定行為的風險。

    protected transient int modCount = 0;
    
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    

在迭代過程中,判斷 modCount 跟 expectedModCount 是否相等,如果不相等就表示已經有其他線程修改了集合。

  • 在 HashMap 的 API 中指出:
    • 由所有 HashMap 類的“collection 視圖方法”所返回的迭代器都是快速失敗的:在迭代器創建之后,如果從結構上對映射進行修改,除非通過迭代器本身的 remove 方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。

注意,迭代器的快速失敗行為不能得到保證,一般來說,存在非同步的并發修改時,不可能作出任何堅決的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴于此異常的程序的做法是錯誤的,正確做法是:迭代器的快速失敗行為應該僅用于檢測程序錯誤。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。