序言:ArrayList是Java集合中的基礎中的基礎,也是面試中常問常考的點,今天咱們來點簡單的,盤盤ArrayListjava
下面我憑本身的經驗大概說一下如何看源碼。面試
注:本篇文章全部分析,針對 jdk1.8版本設計模式
ArrayList 的本質是能夠動態擴容的數組集合,是基於數組去實現的List類數組
ArrayList 的底層的數據結構是 Object[] 類型的數組,默認建立爲空數組,採用懶加載的方式節省空間,每次採起1.5倍擴容的方式安全
ArrayList 是線程不安全的,線程安全的 List 參考 Collections.synchronizedList()
或者 CopyOnWriteArrayList數據結構
大體結構如上圖,先考慮三個問題:dom
1.爲何要繼承 AbstractList,而讓 AbstractList 去實現 List , 並非直接實現呢 ?源碼分析
這裏主要運用了模板方法設計模式,設想一下,jdk 開發做者 還有一個類要實現叫 LinkedList ,它也有何 ArrayList 相同功能的基礎方法,可是因爲是直接實現接口的方式,這個 類 LinkedList 就無法複用以前的方法了ui
2.ArrayList 實現了哪些其餘接口,有什麼用 ?this
RandomAccess 接口: 是一個標誌接口, 代表實現這個這個接口的 List 集合是支持快速隨機訪問的。也就是說,實現了這個接口的集合是支持 快速隨機訪問 策略的,實現了此接口 for循環的迭代效率,會高於 Iterator
Cloneable 接口: 實現了該接口,就可使用 Object.Clone()
方法
Serializable 接口:實現序列號接口,代表該類能夠被序列化
3. 爲何AbstractList 已經實現了 List接口了,做爲父類ArrayList 再去實現一遍呢 ?
做者說這是一個錯誤,一開始拍腦殼以爲可能有用,後來沒用到,但沒什麼影響就留到了如今
public class ArrayList extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; //默認初始容量爲10 private static final int DEFAULT_CAPACITY = 10; //空的對象數組 private static final Object[] EMPTY_ELEMENTDATA = {}; //缺省空對象數組 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //集合容器,transient標識不被序列化 transient Object[] elementData; //容器實際元素長度 private int size; }
1. 使用無參構造器
/** * 默認無參構造器建立的時候其實只是個空數組,使用懶加載節省內存開銷 * Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
2. 建立時,指定集合大小,如何一開始就能預估存儲元素的size,推薦用此種方式
public ArrayList(int initialCapacity) { /** * 若是initialCapacity > 0,就申明一個這麼大的數組 * 若是initialCapacity = 0,就用屬性 EMPTY_ELEMENTDATA * Object[] EMPTY_ELEMENTDATA = {} * 若是initialCapacity < 0,不合法,拋異常 */ if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
能夠看出有四個添加的方式,咱們挑選前兩個經常使用方法講解源碼,其餘基本差很少
1. boolean add(E) 默認在末尾插入元素
/** * 添加一個元素到list的末端 */ public boolean add(E e) { //確保內置的容量是否足夠 ensureCapacityInternal(size + 1); //若是足夠添加加元素放進數組中,size + 1 elementData[size++] = e; //返回成功 return true; } //minCapacity = size + 1 private void ensureCapacityInternal(int minCapacity) { //確認容器容量是否足夠 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //先計算容量大小,elementData是初始建立的集合容器,minCapacity= size + 1 private static int calculateCapacity(Object[] elementData, int minCapacity) { //判斷初始容器是否等於空 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //爲空,就拿size + 1和默認容量10比,誰大返回誰 return Math.max(DEFAULT_CAPACITY, minCapacity); } //不然返回size + 1 return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { //列表結構被修改的次數,用戶迭代時修改列表拋異常(ConcurrentModifyException) modCount++; /** * minCapacity 若是超過容器的長度,則進行擴容,兩種場景 * 1.第一次添加元素,minCapacity = size + 1,size = 0,在上個方法中會 * 與默認的容器容量10進行大小比較最後爲10,那麼10 > 0,初始化容器 * 2.添加到第10個元素,上個方法則返回 size + 1 = 11,11 > 10,須要擴容 * 否則數組裝不下了 */ if (minCapacity - elementData.length > 0) grow(minCapacity); } //容器的擴容方法 private void grow(int minCapacity) { //將擴充前的容器長度賦值給oldCapacity int oldCapacity = elementData.length; //擴容後的newCapacity = 1 + 1/2 = 1.5,因此爲1.5倍擴容 int newCapacity = oldCapacity + (oldCapacity >> 1); /** * 適用於初始建立,elementData爲空數組,length = 0 * oldCapacity = 0,但minCapacity = 10,此處則進行了初始化 */ if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //若是新容量超過容量上限Integer.MAX_VALUE - 8,則會再嘗試擴容 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(); /** * 這一步無非再判斷一次 * size + 1 > Integ er.MAX_VALUE - 8 ? * 若是大的話賦值 Integer.MAX_VALUE * 說明集合最大也就 Integer.MAX_VALUE的長度了 */ return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
2. void add(int,E);在指定位置添加元素
public void add(int index, E element) { //邊界校驗 插入的位置必須在0-size之間 rangeCheckForAdd(index); //不分析了上面有 ensureCapacityInternal(size + 1); //就是將你插入位置的後面的數組日後移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); //放入該位置 elementData[index] = element; //元素個數+1 size++; } private void rangeCheckForAdd(int index) { //插入位置過於離譜,反手給你一個越界異常 if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * src:源對象 * srcPos:源對象對象的起始位置 * dest:目標對象 * destPost:目標對象的起始位置 * length:從起始位置日後複製的長度 */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
總結:
初始容量爲10,1.5倍擴容,擴容最大值爲 Integer.MAX_VALUE,
當咱們調用add方法時,實際方法的調用以下:
由於這幾個都基本相似,咱們就選兩個進行剖析
1. remove(int):經過刪除指定位置上的元素
public E remove(int index) { //檢測index,是否在索引範圍內 rangeCheck(index); //列表結構被修改的次數,用戶迭代時修改列表拋異常 modCount++; //取出要被刪除的元素 E oldValue = elementData(index); //和添加的邏輯類似,算數組移動的位置 int numMoved = size - index - 1; //大於0說明不是尾部 if (numMoved > 0) //將刪除位置後面的數組往刪除位前移一格 System.arraycopy(elementData, index+1, elementData, index, numMoved); /* * 因爲只是將數組前移蓋住了一位,可是長度沒變 * 若是刪除位置不是末尾,此時末尾會有兩個相同元素 * 須要刪除末尾最後一個元素,size - 1 */ elementData[--size] = null; // clear to let GC do its work //返回要刪除的元素值 return oldValue; } /* * 這個方法不過多解釋了,可是筆者認爲這小小的檢查邊界的方法細節寫的不是很完美 * 這個地方沒有主動考慮,index < 0的狀況,異常是後續直接拋的,並非在這 * 後續版本彷佛對這個細節作了糾正 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
2. removeAll(collection c):批量刪除元素
public boolean removeAll(Collection<?> c) { //判斷非空 Objects.requireNonNull(c); //批量刪除 return batchRemove(c, false); } private boolean batchRemove(Collection<?> c, boolean complement) { //用一個引用變量接收容器 final Object[] elementData = this.elementData; //r表明elementData循環次數,w表明不須要刪除的元素個數 int r = 0, w = 0; //修改變更默認爲false boolean modified = false; try { //遍歷集合,遍歷幾回r就是幾,直至r=size for (; r < size; r++) //查看當前集合元素是否不被包含,complement = false if (c.contains(elementData[r]) == complement) //不被包含,那我在當前容器從頭開始放,w是下標 elementData[w++] = elementData[r]; } finally { /** * 若是r沒有成功遍歷完,拋異常了(這是個finally) * 理所固然將後面沒有遍歷完的元素歸爲不被包含 */ if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } //存在不須要被刪除的元素 if (w != size) { //遍歷刪除 for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; //有作過修改變更 modified = true; } } return modified; }
public E set(int index, E element) { //邊界範圍校驗 rangeCheck(index); //取出舊值 E oldValue = elementData(index); //賦上新值 elementData[index] = element; //返回舊值 return oldValue; }
public E get(int index) { //邊界範圍校驗 rangeCheck(index); //經過下標獲取查找的元素 return elementData(index); }