本篇文章記錄經過閱讀JDK1.8 ArrayList源碼,結合自身理解分析其實現原理。java
ArrayList容器類的使用頻率十分頻繁,它具備如下特性:數組
private static final int DEFAULT_CAPACITY = 10; // 擴容因子默認爲10 transient Object[] elementData; // 元素數據 private int size; // 當前ArrayList中實際元素數量 protected transient int modCount = 0; // ArrayList被修改的次數,這個屬性是從java.util.AbstractList繼承下來的
/* * 無參構造器 將elementData初始化爲一個空的數組 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /* * int整形構造器,將elementData初始化爲指定大小的數組 */ 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); } }
將添加數組分解成如下5個步驟
安全
// 添加一個元素時 必須確保當前數組能夠添加一個新的元素,所以會根據capacity去計算 public boolean add(E e) { ensureCapacityInternal(size + 1); // ⑤ 將新元素添加到 擴容以後的數組size++座標上,(注意是size++,先賦值再自增size) elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } // ① 計算容量 private static int calculateCapacity(Object[] elementData, int minCapacity) { /* 若是初始化ArrayList時 是使用無參構造 new ArrayList()
那麼第一次向ArrayList添加元素時會將容量設爲DEFAULT_CAPACITY 10個*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } // ② 確認是否擴容 private void ensureExplicitCapacity(int minCapacity) { // ArrayList被修改次數 + 1 modCount++; /*
若是計算出來的容量 > elementData數組的長度時,那麼會要擴容 由於elementData數組放不下新元素了;
不然的話就不須要擴容
*/ if (minCapacity - elementData.length > 0) grow(minCapacity); } // ③ 擴容 private void grow(int minCapacity) { // 新數組容量 = 老數組容量 * 1.5 int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); /* 可是必需要要保證 新容量 > ①中【計算出來的容量】 ,不然依然放不下新元素 若是出現這種狀況,那麼使用①中【計算出的容量】最爲新數組容量,保證能放下元素 */ if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 數組元素過多 使用Integer.MAX_VALUE 做爲容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // ④ 經過拷貝的方式擴容 調用native層方法 elementData = Arrays.copyOf(elementData, newCapacity); }
// 往ArrayList中插入11個元素 List<Object> list = new ArrayList<>(); for (int i = 0; i < 11; i++) { list.add(new Object()); }
上述代碼很簡單,步驟爲:ide
以該步驟爲例:源碼分析
List<Object> list = new ArrayList<>(); // 初始化elementData爲一個空數組{} , elementData.length = 0ui
i = 0 時this
list.add(new Object());spa
這個操做會經歷上面代碼片斷的①②③④⑤步驟線程
當 1 ≤ i ≤ 9時 3d
list.add(new Object());
在步驟①算計容量時,minCapacity依次計算出來爲 1, 2, 3, 4, 5, 6, 7, 8, 9 都小於elementData.length(當前經歷了第一次擴容 爲10) ,所以不會考慮擴容,直接將新增元素放到指定的下標中
ps: 此步驟modCount每次都會+1
當 i = 9時, 數組被填充滿
當 i = 10 時
list.add(new Object());
此時就不能將第11個元素放到數組中了,須要第二次擴容
能夠看到,上述操做過程當中list經歷了兩次擴容,所以在使用ArrayList的時候能夠考慮使用有參構造器,確認ArrayList的大小,防止擴容發生,影響效率
public void add(int index, E element) { // 檢查下標是否越界 rangeCheckForAdd(index); // 計算容量 考慮是否擴容 ensureCapacityInternal(size + 1); // 經過拷貝數組的方式插入 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
能夠看到向指定位置插入元素和add(E e)方法步驟差很少,只是拷貝方式作了改變
2. 發生擴容時插入過程
public E remove(int index) { // 檢查索引是否越界 rangeCheck(index); // 修改次數 +1 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; }
刪除元素時,是沒有縮容操做的,例如一個ArrayList中elementData僅有10個元素而且存滿了,remove掉9個元素後,size大小是1,可是elementData.length依舊爲10
// 這個方法能夠以當前elementData數組的size實際大小 從新拷貝出一個數組 // 防止上面問題的發生 public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
// 獲取指定元素 public E get(int index) { rangeCheck(index); return elementData(index); } E elementData(int index) { return (E) elementData[index]; }
這個方法很簡單,檢查完索引範圍,直接從elementData數組上去取對應位置的元素
public boolean contains(Object o) { return indexOf(o) >= 0; } // 遍歷elementData數組 查詢知足條件的下標 public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
能夠看到查詢ArrayList中是否包含某個元素時,是經過遍歷整個數組,依次查詢,所以該方法時間複雜度爲O(n)
public Iterator<E> iterator() { return new Itr(); } // 內部類 迭代器 private class Itr implements Iterator<E> { int cursor; // 返回數據的遊標 int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; // 迭代器指望的被修改的次數 與ArrayList.modCount相同, Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { // ① expectedModCount != modCount 時拋出ConcurrentModificationException checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } // 經過迭代器進行刪除,每刪除一個元素時expectedModCount會被從新賦值 public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
前文中屢次提到ArrayList的屬性modCount,內部類Itr中提供的remove方法,在刪除元素成功後,會將expectedModCount進行更新,所以,經過迭代器迭代ArrayList時,不能使用ArrayList.remove(Object o)方法,若是直接使用ArrayList.remove(Object o)進行刪除(這個方法會對modCount減1),會致使迭代器進行下一次迭代時調用next()方法 檢查到expectedModCount與modCount不一樣(上述代碼①處),拋出ConcurrentModificationException,下面的代碼示例將展現正確與錯誤在迭代中刪除元素的操做
// 正確操做 List<Integer> list = new ArrayList<>(); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { iterator.next(); // next()操做會更新遊標cursor iterator.remove(); } // 錯誤操做:將拋出ConcurrentModificationException List<Integer> list = new ArrayList<>(); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer next = iterator.next(); list.remove(next); }
/* size++非原子操做 elementData[size++] = e 等同於 elementData[size] = e; size = size + 1 */ public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
ArrayList是非線程安全的,緣由是在add操做時,因爲size++是非原子操做,多個線程去對同一ArrayList執行add(E e)方法時,會出現下面兩種狀況
1.狀況1:當兩個線程同時進入add()方法,讀取到當前size爲0,並都往0的下標存元素時,就會致使其中的一個值被覆蓋掉的問題
2.狀況2:假設當前ArrayList爲elementData[10],可存放10個元素,而且當前已經存了9個元素了,那麼此時size=9
線程A | 線程B |
add(e1) | add(e2) |
讀到size爲0 | 讀到size也爲0 |
elementData[0] = e1 | |
elementData[0] = e2 |
線程A | 線程B |
add(e1) | add(e2) |
讀到size爲9 不須要擴容 | 讀到size爲9 不須要擴容 |
elementData[9] = e1 | |
size=size+1//size = 10 |
|
elementData[10] = e2 // 因爲沒有擴容致使數組越界 |
|
... | 拋出異常了 |