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修飾,不會被序列化。
/** * 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); } }