如下爲主要介紹要點,從 Java 8 出發:html
從ArrayList自己特色出發,結論以下:java
關注點 | ArrayList相關結論 |
是否容許空的元素面試 |
是 |
是否容許重複的元素 | 是 |
元素有序:讀取數據和存放數據的順序一致數組 |
是 |
是否線程安全 | 否 |
隨機訪問的效率 | 隨機訪問指定索引(即數組的索引)的元素快 |
順序添加元素的效率 | 在不涉及擴容時,順序添加元素速度快;安全 當須要擴容時,涉及到元素的複製,相對較慢多線程 |
刪除和插入元素的效率 | 因涉及到複製和移動後續的元素,相對較慢app |
ArrayList是一個內部以數組方式實現列表、能夠自動擴容的集合。其內部實現有5個重要的屬性,源碼以下:dom
/** * Default initial capacity. * 默認的初始化元素個數(容量),使用ArrayList()建立時(即不指定容量),首次添加元素會進行 * 內部數組的首次擴容,擴容容量就是DEFAULT_CAPACITY = 10 */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. * 用於構造空實例時的默認共享空數組,在使用 ArrayList(0) (即指定容量爲0)或者 * ArrayList(Collection<? extends E> c)且c.size()=0 (即便用空集合來建立), * 就會使用該空數組做爲默認的空實例 */ 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. * 另外一個共享的空數組,使用ArrayList()時默認的空實現,區別於上面的EMPTY_ELEMENTDATA, * 用來判斷添加第一個元素時是否須要按照默認的容量DEFAULT_CAPACITY進行擴容. * 其餘有指定初始容量的ArrayList(即使大小是0),涉及到的擴容便按照默認的規則進行 */ private static final Object[] DEFAULTCAPACITY_EMPTY_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 /** * The size of the ArrayList (the number of elements it contains). * 數組中元素的實際個數 * @serial */ private int size; /** * 用於記錄被修改(增長/刪除/修改等)的次數 */ protected transient int modCount = 0;
ArrayList有3個常規的構造函數。ide
1. 空參構造函數函數
直接使用共享空數組:DEFAULTCAPACITY_EMPTY_ELEMENTDATA 。
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
這裏註釋寫着構造一個初始容量爲10的空數組??? 其實意思是說,經過這個構造函數創建的ArrayList,初始容量都是10,而初始容量則是在第1次添加元素時進行擴容的。後續的添加元素的源碼中,能夠看到正是經過 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 來將默認容量設置爲10的。
2. 指定初始容量的構造函數
比較簡單的按照初始容量構造內部數組,或者是默認的共享空數組(指定初始容量爲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); } }
3. 經過指定的集合進行構造
一樣當指定集合的大小爲0時,也會默認爲共享空數組 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) { // 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; } }
源碼以下:
/** * 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) 調用了 ensureCapacityInternal(size + 1) 來判斷容量是否知足,首先判斷是不是默認的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,如果則說明建立的時候沒有指定容量,此時按照默認容量DEFAULT_CAPACITY = 10 進行首次擴容(size + 1 = 1)。而 ensureCapacityInternal(size + 1)調用了ensureExplicitCapacity(minCapacity) 再一次進行判斷,這個方法記錄了總共進行修改過的次數modCount,同時進行了實際的數組擴容。固然只有實際數組元素個數size超過數組長度時纔會進行擴容。
從這裏能夠看到,若是原先有指定初始容量,那麼後續的擴容都按照原始的容量來進行的,與默認容量10就沒有關係了。
// 沒有指定初始容量時,按照默認的 DEFAULT_CAPACITY 進行擴容 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 實際超過數組長度纔會進行擴容 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
擴容的方法以下。對於沒有溢出的,實際的擴容是原來的1.5倍,這裏使用了位運算,右移一位相似與/2操做,取了一半,可是位操做快。這裏須要說明的是,默認空構造器時創建的ArrayList也是在這裏首次進行擴容的,使用默認容量 DEFAULT_CAPACITY = 10 。
這裏擴容後還須要將原來的元素複製到新的位置中,所以說涉及到的擴容的改動操做都會比較耗時。
/** * 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; /** * 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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
elementData[size++] = e;就是直接的元素賦值,而後增長 size 的大小。
另外還有指定索引添加元素的,代碼以下。總結來講大體步驟以下:
從這裏能夠看出,因涉及到複製和移動其餘的元素,插入元素比較慢。刪除也是相似的。
/** * 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); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
從這兩處源碼能夠看到,ArrayList並無判斷元素是什麼,而是直接存儲了,所以說:ArrayList容許空的或者重複的元素。
刪除元素有2類:
源碼以下。這裏邊界檢查只作了 >= size,由於數組元素自己不會超過size的,而使用 < 0 的index時,對於elementData(index),自己就是IndexOutOfBound的,並不須要直接判斷。
/** * 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); elementData[--size] = null; // clear to let GC do its work return oldValue; } /** * Checks if the given index is in range. If not, throws an appropriate * runtime exception. This method does *not* check if the index is * negative: It is always used immediately prior to an array access, * which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
刪除的步驟相似,只是這裏是遍歷找到第一個指定的元素而已,而後一樣須要進行後面元素的前遷。
/** * 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 * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> 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 <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
1. 按照指定索引查找元素
這裏只作了邊界檢查,若是沒有越界,直接返回指定索引的元素便可,所以說速度比較快。
/** * 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); }
2. 按照指定索引修改元素
指定索引修改元素,按照索引,速度也是比較快。
/** * 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; }
幾種遍歷方式:for循環(按照索引下標)、迭代器遍歷、forEach遍歷。
這裏就是簡單地經過 get(i) 來獲取,略。。。
Iterator 是各類集合類中比較標準的訪問方式,它隱藏了各類集合的內部結構,抽象出統一的訪問方式。迭代器自己3個比較重要的接口以下:
/** * Returns {@code true} if the iteration has more elements. * (In other words, returns {@code true} if {@link #next} would * return an element rather than throwing an exception.) * * @return {@code true} if the iteration has more elements */ boolean hasNext(); /** * Returns the next element in the iteration. * * @return the next element in the iteration * @throws NoSuchElementException if the iteration has no more elements */ E next(); /** * Removes from the underlying collection the last element returned * by this iterator (optional operation). This method can be called * only once per call to {@link #next}. The behavior of an iterator * is unspecified if the underlying collection is modified while the * iteration is in progress in any way other than by calling this * method. * * @implSpec * The default implementation throws an instance of * {@link UnsupportedOperationException} and performs no other action. * * @throws UnsupportedOperationException if the {@code remove} * operation is not supported by this iterator * * @throws IllegalStateException if the {@code next} method has not * yet been called, or the {@code remove} method has already * been called after the last call to the {@code next} * method */ default void remove() { throw new UnsupportedOperationException("remove"); }
具體到ArrayList,調用其 iterator() 方法,返回的則是內部類 Itr 。
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); }
下面看看 Itr 如何處理上面貼出來的3個接口的。
首先默認 cursor = 0,從索引 0 處進行元素的遍歷沒有問題。而元素的最大索引是 size - 1,所以在 hasNext() 中經過 cursor != size 來判斷是否還有下一個元素或者說遍歷結束。另外這裏還記錄了modCount,在後續中會經過此變量的變化來判斷在遍歷過程當中,當前集合是否被修改過,從而拋出 ConcurrentModificationException (fail-fast機制)。
private class Itr implements Iterator<E> { int cursor; // index of next element to return,下一個要返回的元素的索引 int lastRet = -1; // index of last element returned; -1 if no such,上一個返回的索引的索引 int expectedModCount = modCount; // 用於檢查判斷是否有過修改 public boolean hasNext() { return cursor != size;// size表示數組實際元素數量,cursor == size表示已經遍歷結束 } }
接下來看看 next() 方法,它返回當前須要遍歷的元素,若是在調用前當前集合被修改過並且以前記錄的 expectedModCount 沒有被修改過,也即就是 modCount != expectedModCount,則會拋出 ConcurrentModificationException 。
@SuppressWarnings("unchecked") public E next() { checkForComodification(); // 判斷索引是否已經越界 int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); // cursor移動1位 cursor = i + 1; // 返回實際的索引,同時記錄已返回的下標lastRet return (E) elementData[lastRet = i]; } // 檢查在遍歷過程當中,當前集合是否被修改過 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
而當中只有調用 remove() 纔會從新對expectedModCount進行更新,並且也只有調用這裏的 remove 進行元素的移除才能保證安全。
public void remove() { // 沒有先調用next()而先調用remove()明顯是不行的,此時lastRet = -1會直接拋出異常 if (lastRet < 0) throw new IllegalStateException(); // 檢查當前集合是否被修改過 checkForComodification(); try { // 實際上仍是調用ArrayList自己的remove ArrayList.this.remove(lastRet); // 當前集合已經移除了一個元素,對crusor進行復位 cursor = lastRet; // lastRet進行復位,確保下一次remove前判斷有效 lastRet = -1; // 從新記錄modCount expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
這裏 forEach 也只是遍歷獲取elementData[i] ,固然內部會有一些判斷,確保不會越界並且是否會有 ConcurrentModificationException 異常,有必定地安全性。
@Override public void forEach(Consumer<? super E> action) { // 例行地非空判斷 Objects.requireNonNull(action); // 記錄modCount,同時使用final標識說明不容許修改 final int expectedModCount = modCount; @SuppressWarnings("unchecked") final E[] elementData = (E[]) this.elementData; final int size = this.size; // 每次獲取元素前都須要判斷是否索引正常/modCount正常 for (int i=0; modCount == expectedModCount && i < size; i++) { action.accept(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
在 for 循環中遍歷集合的時候使用 remove 會有什麼問題呢?例子以下,例子1 中因爲有實時判斷邊界,所以並無出錯,而例子2 因爲中途進行了 remove 操做而致使 List 的 size 發生變化了,而原來記錄的 size 並無進行更新,再次進行 remove ,rangeCheck即可檢測出越界異常。
// 例子1:沒有報錯 List<String> strList1 = new ArrayList<>(); strList1.add("1"); strList1.add("2"); strList1.add("3"); strList1.add("3"); for (int i = 0; i < strList1.size(); i++) { if ("3".equals(strList1.get(i))) { strList1.remove(i); // remove以後,i++爲3,此時strList1.size()也爲3,正確退出不出錯 } } System.out.println(strList1); // 例子2:Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3 List<String> strList2 = new ArrayList<>(); strList2.add("1"); strList2.add("2"); strList2.add("3"); strList2.add("3"); int size = strList2.size(); // 4 for (int i = 0; i < size; i++) { if ("3".equals(strList2.get(i))) { strList2.remove(i); // remove以後,i = 2,而size仍爲4,i++爲3,仍然知足條件,而此時就越界了 } } System.out.println(strList2);
迭代器中屢次提到了 ConcurrentModificationException 異常,固然也只有在 modCount 和 expectedModCount 不一致時纔會這樣。那何時會出現不一致呢:使用集合的 add 或者 remove 就會改變 modCount ,製造出機會。多線程或者單線程下進行模擬操做均可以,下面舉個單線程例子:
List<String> itrList = new ArrayList<>(); itrList.add("1"); itrList.add("2"); itrList.add("3"); itrList.add("4"); Iterator<String> iterator = itrList.iterator(); while (iterator.hasNext()) { String nextStr = iterator.next(); if ("3".equals(nextStr)) { // 操做1:正常不會報錯??? // itrList.remove("3"); // 操做2:正常不會報錯 iterator.remove(); } if ("2".equals(nextStr)) { // 操做3:Exception in thread "main" java.util.ConcurrentModificationException // itrList.remove("2"); } } System.out.println(itrList);
其中操做2沒有拋出異常能夠理解,畢竟 iterator 自己的 remove 會考慮到 expectedModCount 的修正。可是操做1和操做3一樣是使用集合自己的 remove,可是操做3如期拋出了異常,而操做1並無。其實,操做1中進行remove以後,iterator 內部的 cursor = 3,且 cursor == size,此時 iterator.hasNext() 中 cursor != size 返回 false,所以退出了,而操做3則還會繼續,所以 checkForComodification 時即可檢查出 ConcurrentModificationException。更多可參考 :Java ConcurrentModificationException異常緣由和解決方法
網上copy過來的1道題,這裏拋出來的異常是 IndexOutOfBoundsException 而並不是 ConcurrentModificationException。
其中的 testList.iterator().hasNext() 每次都是返回一個新的 iteraror ,在進行 testList.remove 以後,新的modCount 便賦給了新的 iteraror,並且也沒有對新的 iteraror 進行什麼操做;而 i++ 卻逐漸累計,testList.size() 逐漸變小,當進行到 i = 5 時,實際上 size = 5,此時 testList.remove(5) 便拋出瞭如期的 IndexOutOfBoundsException。
ArrayList<String> testList = new ArrayList<>(); for (int i = 0; i < 10; i++) { testList.add("sh" + i); } for (int i = 0; testList.iterator().hasNext(); i++) { testList.remove(i); System.out.println("test" + testList.get(i)); } // Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 5, Size: 5
ArrayList自己實現了Cloneable和Serializable,可是關鍵的成員變量倒是用了transient進行了修飾,不但願被序列化。
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
從序列化的實現來看,只對elementData中有元素的部分進行了序列化,並無所有元素,這是合理的,通常elementData的容量比實際的size大,沒有必要全部元素都行序列化。這也提升了時間效率,同時節省了空間。
1 /** 2 * Save the state of the <tt>ArrayList</tt> instance to a stream (that 3 * is, serialize it). 4 * 5 * @serialData The length of the array backing the <tt>ArrayList</tt> 6 * instance is emitted (int), followed by all of its elements 7 * (each an <tt>Object</tt>) in the proper order. 8 */ 9 private void writeObject(java.io.ObjectOutputStream s) 10 throws java.io.IOException{ 11 // Write out element count, and any hidden stuff 12 int expectedModCount = modCount; 13 s.defaultWriteObject(); 14 15 // Write out size as capacity for behavioural compatibility with clone() 16 s.writeInt(size); 17 18 // Write out all elements in the proper order. 19 for (int i=0; i<size; i++) { 20 s.writeObject(elementData[i]); 21 } 22 23 if (modCount != expectedModCount) { 24 throw new ConcurrentModificationException(); 25 } 26 }
一、Vector是線程安全的:Vector大部分方法都和ArrayList差很少,可是其實現都添加了synchronized來保證操做的安全性。
二、Vector能夠指定擴容的增加因子capacityIncrement,每次須要擴容時會根據擴容因子進行判斷,直接擴展指定的因子,或者是倍增,以下:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
相比Java7,Java8中增長了 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 並做爲默認空構造器時的空實現,而原來的 EMPTY_ELEMENTDATA 則改成在 指定容量且容量爲0 或者 指定初始化的集合而集合大小也爲0 時的空實現。
1 /** 2 * Shared empty array instance used for empty instances. 3 */ 4 private static final Object[] EMPTY_ELEMENTDATA = {}; 5 6 /** 7 * Shared empty array instance used for default sized empty instances. We 8 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when 9 * first element is added. 10 */ 11 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};