首先咱們須要對 ArrayList 有一個大體的瞭解就從結構來看看吧.java
該類繼承自 AbstractList 這個比較好說編程
這個類實現的接口比較多,具體以下:數組
<!--more-->安全
// 默認大小爲10 private static final int DEFAULT_CAPACITY = 10; // 空數組 private static final Object[] EMPTY_ELEMENTDATA = {}; // 默認的空數組 這個是在傳入無參的是構造函數會調用的待會再 add 方法中會看到 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 用來存放 ArrayList 中的元素 注意他的修飾符是一個 transient 也就是不會自動序列化 transient Object[] elementData; // 大小 private int size;
下面的方法後面標有數字的就是表示重載方法數據結構
裏面只有一個操做就是把 elementData
設置爲 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
這個空數組。框架
// 無參的構造函數,傳入一個空數組 這時候會建立一個大小爲10的數組,具體操做在 add 中 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
這個就是 new 一個數組,若是數組大小爲0就 賦值爲 EMPTY_ELEMENTDATA
dom
// 按傳入的參數建立新的底層數組 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); } }
在這個方法裏面主要就是把這個 Collection 轉成一個數組,而後把這個數組 copy 一下,若是這個接口的 size 爲0 和上面那個方法同樣傳入 EMPTY_ELEMENTDATA
ide
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) // 上面的註釋的意思是說 jdk 有一個 bug 具體來講就是一個 Object 類型的數組不必定可以存放 Object類型的對象,有可能拋異常 // 主要是由於 Object 類型的數組可能指向的是他的子類的數組,存 Object 類型的東西會報錯 if (elementData.getClass() != Object[].class) // 這個操做是首先new 了新的數組,而後再調用 System.arraycopy 拷貝值。也就是產生新的數組 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 傳入的是空的就直接使用空數組初始化 this.elementData = EMPTY_ELEMENTDATA; } }
可是注意一點這裏有一個 jdk 的 bug 也就是一個 Object 類型的數組不必定可以存放 Object類型的對象,有可能拋異常,主要是由於 Object 類型的數組可能指向的是他的子類的數組,存 Object 類型的東西會報錯。 爲了測試這個 bug 寫了幾行代碼測試一下。這個測試是通不過的,就是存在上面的緣由。函數式編程
一個典型的例子就是 咱們建立一個 string 類型的 list 而後調用 toArray 方法發現返回的是一個 string[] 這時候天然就不能隨便存放元素了。函數
class A{ } class B extends A { } public class JDKBug { @Test public void test1() { B[] arrB = new B[10]; A[] arrA = arrB; arrA[0]=new A(); } }
這個方法也很簡單 ,首先進行範圍判斷,而後就是直接更新下標便可。
// 也沒啥好說的就是,設置新值返回老值 public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
這個方法首先調用了 ensureCapacityInternal()
這個方法裏面就判斷了當前的 elementData
是否等於 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
若是是的話,就把數組的大小設置爲 10 而後進行擴容操做,這裏恰好解釋了爲何採用無參構造的List 的大小是 10 ,這裏擴容操做調用的方法是 ensureExplicitCapacity
裏面就幹了一件事若是用戶指定的大小 大於當前長度就擴容,擴容的方法採用了 Arrays.copy
方法,這個方法實現原理是 new 出一個新的數組,而後調用 System.arraycopy
拷貝數組,最後返回新的數組。
public boolean add(E e) { // 當調用了無參構造,設置大小爲10 ensureCapacityInternal(size + 1); // Increments modCount elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { // 若是當前數組是默認空數組就設置爲 10和 size+1中的最小值 // 這也就是說爲何說無參構造 new 的數組大小是 10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 若用戶指定的最小容量 > 最小擴充容量,則以用戶指定的爲準,不然仍是 10 if (minCapacity - elementData.length > 0) grow(minCapacity); } 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 = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
這個方法比較簡單和上面基本同樣,而後只是最後放元素的時候的操做不同,他是採用了 System.arraycopy 從本身向本身拷貝,目的就在於覆蓋元素。 注意一個規律這裏面只要涉及下標的操做的不少不是本身手寫 for 循環而是採用相似的拷貝覆蓋的方法。算是一個小技巧。
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++; }
同理這裏面仍是用了拷貝覆蓋的技巧。 可是有一點注意的就是不用的節點須要手動的觸發 gc ,這也是在 Efftive Java 中做者舉的一個例子。
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; }
這個方法操做很顯然會判斷 e 是否是 null 若是是 null 的話直接採用 ==
比較,不然的話就直接調用 equals
方法而後執行拷貝覆蓋。
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++) // 調用 equals 方法 if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
這個方法就幹了一件事,把數組中的引用全都設置爲 null 以便 gc 。而不是僅僅把 size 設置爲 0 。
// gc 全部節點 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
這個沒啥好說的就是,採用轉數組而後 copy
// 一個套路 只要涉及到 Collection接口的方法都是把這個接口轉成一個數組而後對數組操做 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; }
直接訪問數組下標。
// 沒啥好說的直接去找數組下標 public E get(int index) { rangeCheck(index); return elementData(index); }
這個方法的實現比較有意思,他不是直接截取一個新的 List 返回,而是在這個類的內部還有一個 subList 的內部類,而後這個類就記錄了 subList 的開始結束下標,而後返回的是這個 subList 對象。你可能會想返回的 subList 他不是 List 不會有問題嗎,這裏這個 subList 是繼承的 AbstractList 因此仍是正確的。
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); } // subList 返回的是一個位置標記實例,就是在原來的數組上放了一些標誌,沒有修改或者拷貝新的空間 private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; private final int parentOffset; private final int offset; int size; // other functions ..... }
前面在介紹數據域的時候我就有標註 elementData 是一個 transition 的變量也就是在自動序列化的時候會忽略這個字段。
而後咱們又在源碼中找到到了 write/readObject
方法,這兩個方法是用來序列化 elementData
中的每個元素,也就是手動的對這個字段進行序列化和反序列化。這不是畫蛇添足嗎?
既然要將ArrayList的字段序列化(即將elementData序列化),那爲何又要用transient修飾elementData呢?
回想ArrayList的自動擴容機制,elementData數組至關於容器,當容器不足時就會再擴充容量,可是容器的容量每每都是大於或者等於ArrayList所存元素的個數。
好比,如今實際有了8個元素,那麼elementData數組的容量多是8x1.5=12,若是直接序列化elementData數組,那麼就會浪費4個元素的空間,特別是當元素個數很是多時,這種浪費是很是不合算的。
因此ArrayList的設計者將elementData設計爲transient,而後在writeObject方法中手動將其序列化,而且只序列化了實際存儲的那些元素,而不是整個數組。
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(); } }
所謂的 fast-fail
就是在咱們進行 iterator
遍歷的時候不容許調用 Collection
接口的方法進行對容器修改,不然就會拋異常。這個實現的機制是在 iterator
中維護了兩個變量,分別是 modCount
和 expectedModCount
因爲 Collection
接口的方法在每次修改操做的時候都會對 modCount++
因此若是在 iterator
中檢測到他們不相等的時候就拋異常。
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; final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
這個是一個函數式編程的方法,看看他的參數 forEach(Consumer<? super E> action)
頗有意思裏面接受是一個函數式的接口,咱們裏面回調了 Consumer
的 accept
因此咱們只須要傳入一個函數接口就能對每個元素處理。
@Override public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); final int expectedModCount = modCount; @SuppressWarnings("unchecked") final E[] elementData = (E[]) this.elementData; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { //回調 action.accept(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
寫了一段測試代碼,可是這個方法不經常使用,主要是 Collection 是能夠本身生成 Stream 對象,而後調用上面的方法便可。這裏提一下。
public class ArrayListTest { @Test public void foreach() { ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(1); list.add(4); list.add(6); list.forEach(System.out::print); //打印每一次元素。 } }
底層調用了 Arrays.sort 方法沒什麼好說的。
public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
這個和 forEach 差很少,就是回調寫好了。
以上基本是把 ArrayList
的重要的方法和屬性介紹完了,咱們已經比較清楚他底層的實現和數據結構了。而後提到 ArrayList
天然也少不了一個比較古老的容器 Vector
這個容器真的和 ArrayList
太像了。由於你會發現他們連繼承和實現的接口都是同樣的。可是也會有一些不一樣的地方,下面分條介紹一下。
在 Vector
中基本全部的方法都是 synchronized
的方法,因此說他是線程安全的 ArrayList
構造方法不同,在屬性中沒有兩個比較特殊的常量,因此說他的構造方法直接初始化一個容量爲 10 的數組。而後他有四個構造方法。
遍歷的接口不同。他仍是有 iterator
的可是他之前的遍歷的方法是 Enumeration
接口,經過 elements
獲取 Enumeration
而後使用 hasMoreElements
和 nextElement
獲取元素。
缺乏一些函數式編程的方法。