前言:做爲菜鳥,須要常常回頭鞏固一下基礎知識,今天看看 jdk 1.8 的源碼,這裏記錄 ArrayList 的實現。html
ArrayList 是有序的集合;java
底層採用數組實現對數據的增刪查改;算法
不是線程安全的;數組
有自動擴容的功能。緩存
一、ArrayList 是實現了 List 接口的可變數據,非同步實現,並容許包括 null 在內的全部元素。安全
二、底層採用數組實現。併發
三、在數組增長時,會進行擴容,但因爲底層採用的數組實現,因此擴容時會將老數組中的元素拷貝到一份新的數組中,因此性能代價很高框架
四、採用了 Fail-Fast 機制,面對併發的修改時,迭代器會拋出異常,而不是冒着在未來某個不肯定時間發生任意不肯定行爲的風險函數
五、remove 方法會經過 System.arraycopy() 方法讓下標到數組末尾的元素向前移動一個單位,並把最後一位的值置空,方便 GC。post
參考:https://blog.csdn.net/zero__007/article/details/52166306
transient 用來表示一個域不是該對象序行化的一部分,當一個對象被序行化的時候,transient 修飾的變量的值是不包括在序行化的表示中的。可是 ArrayList 又是可序行化的類,elementData 是 ArrayList 具體存放元素的成員,用 transient 來修飾 elementData,豈不是反序列化後的 ArrayList 丟失了原先的元素?
其實玄機在於 ArrayList 中的兩個方法:
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) 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 behavioural 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(); } }
/** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
ArrayList 在序列化的時候會調用 writeObject,直接將 size 和 element 寫入 ObjectOutputStream;反序列化時調用 readObject,從 ObjectInputStream 獲取 size 和 element,再恢復到 elementData。
爲何不直接用 elementData 來序列化,而採用上訴的方式來實現序列化呢?緣由在於 elementData 是一個緩存數組,它一般會預留一些容量,等容量不足時再擴充容量,那麼有些空間可能就沒有實際存儲元素,採用上訴的方式來實現序列化時,就能夠保證只序列化實際存儲的那些元素,而不是整個數組,從而節省空間和時間。
兩個雖然都爲空數組,但用途稍微有點不一致。
其中,EMPTY_ELEMENTDATA 用於構造器中給出了初始化容量爲 0 時的數組。代碼以下:
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); } }
其中,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 用於默認構造器的數組,主要用於在第一次增長元素時判斷是否須要給出默認容量 10 的大小(grow() 方法用於擴容)。這裏之因此不直接 new 一個初始容量爲 10 的數組,我想是由於有時咱們會 new 一個 ArrayList(),可是並不會添加數據,這樣就能夠節約空間。代碼以下:
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { // 判斷是不是默認構造函數構造的默認空數組實例,若是是就給出默認容量 10 和 size + 1 的最大值 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); }
源碼中,每次在 add() 一個元素時,ArrayList 都須要對這個 List 的容量進行一個判斷。若是容量夠,直接添加,不然須要進行擴容,調用 grow() 方法。擴容調用的是 grow() 方法,經過 grow() 方法中調用的 Arrays.copyof() 方法進行對原數組的複製,再經過調用 System.arraycopy() 方法進行復制,達到擴容的目的。
源碼中,能夠看出有三種狀況:(這裏參考 https://blog.csdn.net/u010890358/article/details/80515284)
(一)若是當前數組是由默認構造方法生成的空數組而且第一次添加數據。此時 minCapacity 等於默認的容量(10),那麼根據源碼中的邏輯能夠看到最後數組的容量會從 0 擴容成 10。而之後的擴容按照當前容量的1.5 倍進行擴容。1.5 倍這裏用了右移一位,不明白的能夠自行百度。
(二)若是當前數組是由自定義初始容量構造方法建立而且指定初始容量爲 0。此時 minCapacity 等於 1,newCapacity = 0,那麼根據下面邏輯能夠看到最後數組的容量會從0變成1。這邊能夠看到一個嚴重的問題,一旦咱們執行了初始容量爲 0,那麼根據下面的算法前四次擴容每次都 +1,在第5次添加數據進行擴容的時候纔是按照當前容量的1.5倍進行擴容。
(三)若是當擴容量(newCapacity)大於 ArrayList 數組定義的最大值後會調用 hugeCapacity 來進行判斷。若是 minCapacity 已經大於 Integer 的最大值(溢出爲負數)那麼拋出 OutOfMemoryError(內存溢出)不然的話根據與 MAX_ARRAY_SIZE 的比較狀況肯定是返回 Integer 最大值仍是 MAX_ARRAY_SIZE。這邊也能夠看到 ArrayList 容許的最大容量就是 Integer 的最大值(-2 的 31 次方~ 2 的 31 次方減 1)。
源碼以下:
//ArrayList 擴容的核心方法,此方法用來決定擴容量並擴容 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; if (newCapacity - MAX_ARRAY_SIZE > 0) //當擴容量(newCapacity)大於ArrayList數組定義的最大值後會調用hugeCapacity來進行判斷。若是minCapacity已經大於Integer的最大值(溢出爲負數)那麼拋出OutOfMemoryError(內存溢出)不然的話根據與MAX_ARRAY_SIZE的比較狀況肯定是返回Integer最大值仍是MAX_ARRAY_SIZE。這邊也能夠看到ArrayList容許的最大容量就是Integer的最大值(-2的31次方~2的31次方減1)。 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; } // ArrayList 的成員變量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
請移步: Java集合框架——容器的快速報錯機制 fail-fast 是什麼?
//默認的初始化容量 private static final int DEFAULT_CAPACITY = 10; //空數組,用於 使用構造器給出初始容量爲0時的默認空數組 private static final Object[] EMPTY_ELEMENTDATA = {}; //默認的數組,用於 使用默認構造器建立的默認空數組,主要用於後面第一次增長數據時判斷是否須要給出默認容量 10 的大小 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //用於存儲 ArrayList 的元素,這裏就能夠看出 ArrayList 的底層就是數組 transient Object[] elementData; // non-private to simplify nested class access //大小 private int size; //記錄被修改的次數,用於迭代器迭代時保證數據沒有被修改過 protected transient int modCount = 0; //數組大小的最大值 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
默認構造方法:
註釋說實例化了一個容量爲 10 的數組,但其實這裏返回的是一個空數組,是在數組第一次增長數據時經過擴容達到的初始容量爲 10 的數組。前面解惑的二、爲何有兩個默認空數組的成員變量?也提到了。
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_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); } }
生成一個帶數據的 ArrayList 實例:
/** * 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; } }
參考:
https://blog.csdn.net/u010890358/article/details/80515284
全部的集合框架:
http://www.runoob.com/java/java-collections.html
https://blog.csdn.net/qq_25868207/article/details/55259978