1. 程式人生 > >Java11 ArrayList類詳解

Java11 ArrayList類詳解

ArrayList

ArrayList是List介面的可變大小陣列的實現。實現所有可選連結串列操作,允許所有元素,包括null。除了實現了List介面之外,這個類還提供了操作用來內部儲存連結串列的陣列大小的方法(這個類大致等同於Vector,除了他是非同步的以外)。

size()、isEmpty()、get()、set()、iterator()、listIterator()操作以恆定的時間執行。add()操作以O(n)的時間複雜度執行,其他的所有操作則以線性的時間執行(粗略的說)。常量因子相比與LinkedList較低。

每個ArrayList例項有一個capacity(容量)。capacity是用來儲存連結串列元素的陣列的大小。它總是至少和list大小一樣大。當元素被加入到ArrayList後,它的capacity會自動擴容。除了增加一個元素有恆定的攤銷時間成本之外,增長策略的細節未被指定。

在新增大量的元素之前,一個應用可以使用ensureCapacity()操作提高一個例項的capacity。這可以減少增量重分配的數量(This may reduce the amount of incremental reallocation)。

要注意的是ArrayList不是同步的。如果多執行緒併發的訪問一個ArrayList例項,至少有一個執行緒會修改連結串列結構,必須在它的外部進行非同步控制。(一個結構化的修改是指增加或刪除一個或多個元素的操作,或者顯式改變底層陣列的大小;僅僅設定一個元素的值不是一個結構化的修改。)一個典型的實現是通過對某個物件的同步來自然封裝這個連結串列。(This is typically accomplished by synchronizing on some object that naturally encapsulates the list.)

如果沒有這種物件,這個連結串列則需要用Collections.synchronizedList()方法來封裝。這最好是在建立的時候完成,以防止偶然的對連結串列的非同步訪問。

List list = Collections.synchronizedList(new ArrayList(...));

類的iterator()和listIterator(int)方法返回的迭代器都是fail-fast的:如果迭代器建立之後,連結串列在任意時刻被結構化的修改,除了迭代器自己的remove()、add()方法,迭代器將丟擲ConcurrentModificationException異常。因此,面對併發修改,迭代器將快速失敗並清空,而不是在未來的不確定的時刻冒著任意的風險以及不確定的行為。

注意,迭代器的fail-fast行為不能被保證一定發生,通常來說,當非同步併發修改發生時,不可能做出任何硬性保證。Fail-fast迭代器基於最大努力丟擲ConcurrentModificationException異常。因此,這樣的做法的是錯的,寫一個程式,依賴於這個異常來保證程式的正確性:迭代器的fail-fast行為應該只被用來檢查bug(個人理解是如果發生併發修改,並不一定保證丟擲ConcurrentModificationException異常,因此程式的正確性不應該依賴此異常)。

1.預設容量capacity

/**
 * Default initial capacity.
*/ private static final int DEFAULT_CAPACITY = 10;

2.儲存ArrayList元素的陣列elementData

/**
 * 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

ArrayList的容量就是陣列的長度。

任何elementData等於DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList,當其第一個元素被新增進來時,elementData的長度將被擴充套件為DEFAULT_CAPACITY。

3.ArrayList包含元素的數量size

/**
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
*/ private int size;

4.構造器

ArrayList包含三種構造器:

1.ArrayList():將elementData初始化為空陣列DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

2.ArrayList(int initialCapacity):將element初始化為容量為initialCapacity的陣列,當initialCapacity為0是,初始化為EMPTY_ELEMENTDATA

3.ArrayList(Collection<? extends E> c):使用集合來初始化elementData,如果集合的length為0,則初始化為EMPTY_ELEMENTDATA。

    /**
     * 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;
    }

    /**
     * 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) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

5.Fast-fail與結構化修改

ArrayList繼承於AbstractList,AbstractList中的域modCount用來統計結構化修改的次數。

結構化修改是指改變連結串列的大小,或者其他擾亂它的方式,可能導致正在進行的迭代可能產生不正確的結果。

    /**
     * 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;
View Code

在序列化以及迭代(forEach)等操作的前後,需要記錄modCount的值進行對比,如果不相等,則丟擲ConcurrentModificationException異常。

    /**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioral compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     */
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        final Object[] es = elementData;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++)
            action.accept(elementAt(es, i));
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

 

6.重要的方法

  • public void ensureCapacity(int minCapacity)

  引數minCapacity是要擴容的最小大小,要進行擴容必須滿足:

    1.minCapacity大於現在elementData的長度。

    2.不滿足(elementData為預設空陣列&&要擴容的最小大小小於DEFAULT_CAPACITY

    /**
     * Increases the capacity of this {@code ArrayList} instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            modCount++;
            grow(minCapacity);
        }
    }
  • private int newCapacity(int minCapacity)

  返回至少和給定minCapacity一樣大小的容量。

  如果可以的話將返回elementData大小的1.5倍。

  除非給定minCapacity大於MAX_ARRAY_SIZE,否則容量不應超過MAX_ARRAY_SIZE。

  /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by 50% if that suffices.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
          //之前介紹elementData的時候說到,elementData為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
          //的空ArrayList,當第一次新增元素時,容量被擴充為DEFAULT_CAPACITY
return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }
  • private Object[] grow(int minCapacity)
  • private Object[] grow()
    /**
     * 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
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
        return grow(size + 1);
    }
  • private void add(E e, Object[] elementData, int s)
  • public boolean add(E e)
  • public add(int index, E element)

add(E e)執行時,根據呼叫關係,當要進行擴容時,最終會呼叫newCapacity(),容量為擴充為element.length*1.5和minCapacity中的較大者(通常情況是這樣,具體參見程式碼newCapacity())。

add(int index, E element)執行時,如需進行擴容會先進行擴容,然後通過System.arraycopy進行移位,再將元素插入指定位置。

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

    /**
     * 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);
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }
  • public E remove(int index)
  • public boolean remove(Object o)
  • private void fastRemove(Object[] es, int i)

  remove(int index)主要呼叫了fastRemove()來進行刪除,fastRemove()用的也是System.arraycopy()。

  remove(Object o)先找到該物件的索引,然後呼叫fastRemove()。

    /**
     * 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) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * {@code Objects.equals(o, get(i))}
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }

    /**
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

7.序列化

ArrayList 基於陣列實現,並且具有動態擴容特性,因此儲存元素的陣列不一定都會被使用,那麼就沒必要全部進行序列化。

可以發現,elementData被transient修飾,不會被序列化。

transient 關鍵字

    /**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioral compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * Reconstitutes the {@code ArrayList} instance from a stream (that is,
     * deserializes it).
     * @param s the stream
     * @throws ClassNotFoundException if the class of a serialized object
     *         could not be found
     * @throws java.io.IOException if an I/O error occurs
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // like clone(), allocate array based upon size not capacity
            SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
            Object[] elements = new Object[size];

            // Read in all elements in the proper order.
            for (int i = 0; i < size; i++) {
                elements[i] = s.readObject();
            }

            elementData = elements;
        } else if (size == 0) {
            elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new java.io.InvalidObjectException("Invalid size: " + size);
        }
    }