Java ArrayList源碼分析(含擴容機制等重點問題分析)

寫在最前面

這個項目是從20年底就立好的 flag,通過幾年的學習,回過頭再去看不少知識點又有新的理解。因此趁着找實習的準備,結合之前的學習儲備,建立一個主要針對應屆生和初學者的 Java 開源知識項目,專一 Java 後端面試題 + 解析 + 重點知識詳解 + 精選文章的開源項目,但願它能伴隨你我一直進步!java

說明:此項目內容參考了諸多博主(已註明出處),資料,N本書籍,以及結合本身理解,從新繪圖,從新組織語言等等所制。我的之力綿薄,或有不足之處,在所不免,但更新/完善會一直進行。你們的每個 Star 都是對個人鼓勵 !但願你們能喜歡。git

注:全部涉及圖片未使用網絡圖牀,文章等均開源提供給你們。github

項目名: Java-Ideal-Interview面試

Github 地址: Java-Ideal-Interview - Github 後端

Gitee 地址:Java-Ideal-Interview - Gitee(碼雲) 數組

持續更新中,在線閱讀將會在後期提供,若認爲 Gitee 或 Github 閱讀不便,可克隆到本地配合 Typora 等編輯器溫馨閱讀安全

若 Github 克隆速度過慢,可選擇使用國內 Gitee 倉庫網絡

一 ArrayList 源碼分析(含擴容機制分析)

1. ArrayList 概述

1.1 List 是什麼?

List 在 Collection中充當着一個什麼樣的身份呢?——有序的 collection(也稱爲序列)

實現這個接口的用戶以對列表中每一個元素的插入位置進行精確地控制。用戶能夠根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。與 set 不一樣,列表一般容許重複的元素。

1.2 ArrayList 是什麼?

ArrayList 的底層就是一個數組,依賴其擴容機制(後面會提到)它可以實現容量的動態增加,因此 ArrayList 就是數據結構中順序表的一種具體實現。

其特色爲:查詢快,增刪慢,線程不安全,效率高。

1.3 順序表的優缺點

優勢:

  1. 邏輯與物理順序一致,順序表可以按照下標直接快速的存取元素
  2. 無須爲了表示表中元素之間的邏輯關係而增長額外的存儲空間

缺點:

  1. 線性表長度須要初始定義,經常難以肯定存儲空間的容量,因此只能以下降效率的代價使用擴容機制
  2. 插入和刪除操做須要移動大量的元素,效率較低

1.4 時間複雜度證實

讀取

還記的這個公式嗎?

$$Loc(a_i) = Loc(a_1) + (i -1)*L$$

經過這個公式咱們能夠在任什麼時候候計算出線性表中任意位置的地址,而且對於計算機所使用的時間都是相同的,即一個常數,這也就意味着,它的時間複雜度爲 O(1)

插入和刪除

咱們以插入爲例子

  • 首先最好的狀況是這樣的,元素在末尾的位置插入,這樣不管該元素進行什麼操做,均不會對其餘元素產生什麼影響,因此它的時間複雜度爲 O(1)
  • 那麼最壞的狀況又是這樣的,元素正好插入到第一個位置上,這就意味着後面的全部元素所有須要移動一個位置,因此時間複雜度爲 O(n)
  • 平均的狀況呢,因爲在每個位置插入的機率都是相同的,而插入越靠前移動的元素越多,因此平均狀況就與中間那個值的必定次數相等,爲 (n - 1) / 2 ,平均時間複雜度仍是 O(n)

總結

讀取數據的時候,它的時間複雜度爲 O(1),插入和刪除數據的時候,它的時間複雜度爲 O(n),因此線性表中的順序表更加適合處理一些元素個數比較穩定,查詢讀取多的問題

2. 核心源碼分析

2.1 類聲明

先來看一下類的聲明,有一個繼承(抽象類)和四個接口關係

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{ 
    // 源碼具體內容... 
}
  • RandomAccess 是一個標誌接口(Marker)只要 List 集合實現這個接口,就能支持快速隨機訪問(經過元素序號快速獲取元素對象 —— get(int index)
  • Cloneable :實現它就能夠進行克隆(clone()
  • java.io.Serializable :實現它意味着支持序列化,知足了序列化傳輸的條件

2.2 類成員

下面接着看一些成員屬性

// 序列化自動生成的一個碼,用來在正反序列化中驗證版本一致性。
private static final long serialVersionUID = 8683452581122892189L;

/**
 * 默認初始容量大小爲10
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 指定 ArrayList 容量爲0(空實例)時,返回此空數組
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 與 EMPTY_ELEMENTDATA 的區別是,它是默認返回的,而前者是用戶指定容量爲 0 才返回
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 具體存放元素的數組
 * 保存添加到 ArrayList 中的元素數據(第一次添加元素時,會擴容到 DEFAULT_CAPACITY = 10 ) 
 */
transient Object[] elementData; // non-private to simplify nested class access

/**
 * ArrayList 實際所含元素個數(大小)
 */
private int size;

2.4 構造方法

/**
 * 帶參構造函數,參數爲用戶指定的初始容量
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 參數大於0,建立 initialCapacity 大小的數組
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 參數爲0,建立空數組(成員中有定義)
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 其餘狀況,直接拋異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

/**
 * 默認無參構造函數,初始值爲 0
 * 也說明 DEFAULT_CAPACITY = 10 這個容量
 * 不是在構造函數初始化的時候設定的(而是在添加第一個元素的時候)
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 構造一個包含指定 collection 的元素的列表
 * 這些元素是按照該 collection 的迭代器返回它們的順序排列的。
 */
public ArrayList(Collection<? extends E> c) {
    // 將給定的集合轉成數組
    elementData = c.toArray();
    // 若是數組長度不爲 0
    if ((size = elementData.length) != 0) {
        // elementData 若是不是 Object 類型的數據,返回的就不是 Object 類型的數組
        if (elementData.getClass() != Object[].class)
            // 將不是 Object 類型的 elementData 數組,賦值給一個新的 Object 類型的數組
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 數組長度爲 0 ,用空數組代替
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

2.5 最小化實例容量方法

/**
 * 最小化實例容量方法,能夠根據實際元素個數,將數組容量優化,防止浪費
 */
public void trimToSize() {
    modCount++;
    // 數組容量大於實際元素個數(例如10個元素,卻有15個容量)
    if (size < elementData.length) {
        // 根據元素實際個數,從新最小化實例容量
        elementData = (size == 0)
            ? EMPTY_ELEMENTDATA
            : Arrays.copyOf(elementData, size);
    }
}

2.5 擴容方法

這裏只是按照順序介紹,後面還會專門針對擴容進行一個分析

/**
 * 增長ArrayList實例的容量,若是有必要,確保它至少能夠保存由最小容量參數指定的元素數量。
 */
public void ensureCapacity(int minCapacity) {
    //若是元素數組不爲默認的空,則 minExpand 的值爲0,反之值爲10
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;
    // 若是最小容量大於已有的最大容量
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

/**
 * 計算最小擴容量(被調用)
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
     // 若是元素數組爲默認的空
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 獲取「默認的容量」和「傳入參數 minCapacity 」二者之間的最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

/**
 * 獲得最小擴容量
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}


/**
 * 判斷是否須要擴容
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    // 若是最小容量比數組的長度還大
    if (minCapacity - elementData.length > 0)
        // 就調用grow方法進行擴容
        grow(minCapacity);
}

/**
 * 要分配的最大數組大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * ArrayList 擴容的核心方法
 */
private void grow(int minCapacity) {
    // 將當前元素數組長度定義爲 oldCapacity 舊容量
    int oldCapacity = elementData.length;
    // 新容量更新爲舊容量的1.5倍
    // oldCapacity >> 1 爲按位右移一位,至關於 oldCapacity 除以2的1次冪
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 而後檢查新容量是否大於最小須要容量,若還小,就把最小須要容量看成數組的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 再檢查新容量是否超出了ArrayList 所定義的最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 若超出,則調用hugeCapacity()
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
    
/**
 * 比較minCapacity和 MAX_ARRAY_SIZE
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

2.6 常規方法

/**
 * 返回元素數量
 */
public int size() {
    return size;
}

/**
 * 此列表元素數量爲 0 則返回 true
 */
public boolean isEmpty() {
    return size == 0;
}

/**
 * 此列表含有指定元素,則返回true
 */
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

/**
 * 返回此列表中元素首次出現位置的索引
 * 若不包含此元素,則返回 -1
 */
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        // 本質就是循環 equals 比對
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

/**
 * 返回此列表中指定元素的最後一次出現的索引
 * 若是此列表不包含元素,則返回 -1
 */
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        // 逆向循環 equals 比對
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}


/**
 * 返回 ArrayList 實例的淺拷貝
 */
public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        // 實現數組的複製,參數爲被複制者的參數
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

/**
 * 返回一個包含此列表中全部元素的數組(理解爲將集合轉爲數組便可)
 */
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

/**
 * 將list轉化爲你所須要類型的數組,而後返回
 */
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    // 複製用法,下面專題會講解此內容
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

// Positional Access Operations

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

/**
 * 返回此列表中指定位置的元素。
 */
public E get(int index) {
    // index 範圍檢查
    rangeCheck(index);
    return elementData(index);
}

/**
 * 用指定的元素替換此列表中指定位置的元素。
 */
public E set(int index, E element) {
    // index 範圍檢查
    rangeCheck(index);
    // 根據 index 找到想替換的舊元素
    E oldValue = elementData(index);
    // 替換元素
    elementData[index] = element;
    return oldValue;
}

/**
 * 將指定的元素追加到此列表的末尾。
 */
public boolean add(E e) {
    // 確認 list 容量,嘗試容量加 1,看看有無必要擴容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 賦值
    elementData[size++] = e;
    return true;
}

/**
 * 在此列表中的指定位置插入指定的元素
 * 再將從index開始以後的全部成員後移一個位置;將element插入index位置;最後size加1。
 */
public void add(int index, E element) {
    // 調用 rangeCheckForAdd 對 index 進行範圍檢查
    rangeCheckForAdd(index);
    // 保證容量足夠
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 本身複製本身,而後達到 index 以後所有元素向後挪一位的效果
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 而後將 index 賦值爲指定的元素
    elementData[index] = element;
    size++;
}

/**
 * 移除該列表中指定位置的元素。 將任何後續元素移動到左側(從其索引中減去一個元素)。
 */
public E remove(int index) {
    // 調用 rangeCheckForAdd 對 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;
}

/**
 * 從集合中移除第一次出現的指定元素
 */
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        // 也很簡單,就是一個循環 equals 判斷,而後移除
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

/*
 * 跳過範圍檢查的刪除方式,與remove(Object o)相同
 */
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
}

/**
 * 從列表中刪除全部元素。
 */
public void clear() {
    modCount++;
    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        // 元素所有設爲 null
        elementData[i] = null;
    // 長度設爲 0
    size = 0;
}

/**
 * 按指定集合的Iterator返回的順序
 * 將指定集合中的全部元素追加到此列表的末尾。
 */
public boolean addAll(Collection<? extends E> c) {
    // 轉爲數組
    Object[] a = c.toArray();
    // 拿到待添加指定數組的長度
    int numNew = a.length;
    // 確認 list 容量,嘗試容量加上 numNew,看看有無必要擴容
    ensureCapacityInternal(size + numNew);  // Increments modCount
    // 利用 arraycopy 指定數組a的元素追加到當前數組 elementData 後
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

/**
 * 按指定集合的Iterator返回的順序
 * 將指定集合中的全部元素添加到此列表中,從指定位置開始
 * 
 */
public boolean addAll(int index, Collection<? extends E> c) { 
    rangeCheckForAdd(index);
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    // 計算須要移動的元素
    int numMoved = size - index;
    if (numMoved > 0)
        // 實現元素指定位置的插入,本質仍是 arraycopy 自身
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

/**
 * 刪除指定索引範圍內的元素(fromIndex - toIndex)
 * 將任何後續元素移動到左側(減小其索引)。
 */
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

/**
 * 檢查給定的索引是否在範圍內。
 */
private void rangeCheck(int index) {
    // 下標越界就直接拋異常
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * 另外一個版本,針對add 和 addAll使用
 */
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * 與上面套娃使用
 */
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}

/**
 * 今後列表中刪除指定集合中包含的全部元素。
 */
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

/**
 * 僅保留此列表中包含在指定集合中的元素。即刪掉沒有的部分
 */
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

/**
 * 刪除的具體邏輯,下面會有專題講解
 */
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            // 經過循環判斷數組中有沒有指定數組中的每個值,complement 是參數傳遞的
            if (c.contains(elementData[r]) == complement)
                // 就將原數組的r位置的數據覆蓋掉w位置的數據
                // r位置的數據不變,並其w自增,r自增
                // 不然,r自增,w不自增
                // 本質:把須要移除的數據都替換掉,不須要移除的數據前移
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

// writeObject readObject 序列化相關的省略
   
/**
 * 列表迭代器:List集合特有的迭代器
 */
public ListIterator<E> listIterator(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index);
    return new ListItr(index);
}


public ListIterator<E> listIterator() {
    return new ListItr(0);
}

// foreach 遍歷等同於 iterator
public Iterator<E> iterator() {
    return new Itr();
}


private class Itr implements Iterator<E> {
    // 下一個要訪問的元素下標
    int cursor; 
    // 上一個要訪問的元素下標
    int lastRet = -1; 
    // 表明對 ArrayList 修改次數的指望值,初始值爲 modCount
    int expectedModCount = modCount;

    Itr() {}

    // 下標若是
    public boolean hasNext() {
        return cursor != size;
    }

    /**
     * 剛開始cursor = 0,lastRet = -1
     * 整個過程結束 cursor 和 lastRet 都會自增 1
     */
    @SuppressWarnings("unchecked")
    public E next() {
        // 跳轉本質是判斷 modCount 是否等於 expectedModCount
        checkForComodification();
        int i = cursor;
       // 判斷 cursor 是否超過集合大小和數組長度
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        // 將 cursor 賦值給 lastRet,而後把此下標處的元素返回
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        // 先判斷 lastRet 的值是否小於 0
        if (lastRet < 0)
            throw new IllegalStateException();
        // 跳轉本質是判斷 modCount 是否等於 expectedModCount
        checkForComodification();

        try {
            // 直接調用 ArrayList 的 remove 方法刪除下標爲 lastRet 的元素
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    
    // forEachRemaining 略

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

3. 重點內容分析

3.1 擴容機制再分析

3.1.1 ArrayList 是如何被初始化的

ArrayList 提供了 1 個無參構造和 2 個帶參構造來初始化 ArrayList ,咱們在建立 ArrayList 時,常用無參構造的方式,其本質就是初始化了一個空數組,直到向數組內真的添加元素的時候纔會真的去分配容量。例如:向數組中添加第一個元素,數組容量擴充爲 10

補充:JDK7 無參構造 初始化 ArrayList 對象時,直接建立了長度是 10 的 Object[] 數組elementData

3.1.2 擴容機制流程分析(無參構造爲例)

3.1.2.1 add()

通常來講,都是經過 add 方法觸發擴容機制,咱們拿最簡單的尾部追加的 add() 方法舉例

/**
 * 將指定的元素追加到此列表的末尾。
 */
public boolean add(E e) {
    // 確認 list 容量,嘗試容量加 1,看看有無必要擴容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 賦值
    elementData[size++] = e;
    return true;
}

核心要點就這一句 ensureCapacityInternal(size + 1);

3.1.2.2 ensureCapacityInternal()

追蹤進去

/**
 * 獲得最小擴容量
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

方法內調用了 ensureExplicitCapacity() 方法,參數是 calculateCapacity(elementData, minCapacity)

先來分析一下這個參數的結果是什麼,聚焦到 calculateCapacity() 方法中去

3.1.2.3 calculateCapacity()
/**
 * 計算最小擴容量(被調用)
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
     // 若是元素數組爲默認的空
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 獲取「默認的容量」和「傳入參數 minCapacity 」二者之間的最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

也很簡單,就是爲了計算出一個最小擴容量,當元素爲初次初始化時,數組還沒進過擴容,是一個空數組,因此會走 if 這個判斷,並且當時傳入的 size + 1 也就是 minCapacity 的值爲 0 + 1 = 1 ,通過一個取大值的操做,與默認的 DEFAULT_CAPACITY 進行比對,天然返回的就是 10。

若是數組已經不是爲空了,就直接返回一個 minCapacity (size + 1)就能夠了

3.1.2.4 ensureExplicitCapacity

ensureCapacityInternal 方法內調用了 ensureExplicitCapacity(參數已經計算出來了) 方法

繼續去看它

/**
 * 判斷是否須要擴容
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    // 若是最小容量比數組的長度還大
    if (minCapacity - elementData.length > 0)
        // 就調用grow方法進行擴容
        grow(minCapacity);
}

此方法的核心就是 if 判斷這個數組需不須要擴容,能夠分爲三種狀況

  • add 第 1 個元素時:此時數組還只是一個被初始化過的空數組,minCapacity 通過 calculateCapacity 計算會返回 DEFAULT_CAPACITY 的默認值 10,而 elementData.length 也天然是 0,因此 minCapacity - elementData.length > 0 是成立的,直接進入 grow(minCapacity); 開始擴容。
  • add 第 2 到 10 個元素的時候(以 2 舉例):此時 minCapacity = size + 1 = 1 + 1 = 2 ,而 elementData.length 已經在添加第 1 個元素後等於 10 了。因此 minCapacity - elementData.length > 0 就不成立了,因此不會進入 grow(minCapacity); ,也不會擴容

    • 添加第 3 ... 10 個元素的時候,都是同樣的。
  • add 第 11 個元素的時候,minCapacity 變成了 11,比 10 還要大,因此又一次進去擴容了
3.1.2.5 grow()

這裏是真正去執行擴容邏輯的代碼

/**
 * 要分配的最大數組大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * ArrayList 擴容的核心方法
 */
private void grow(int minCapacity) {
    // 將當前元素數組長度定義爲 oldCapacity 舊容量
    int oldCapacity = elementData.length;
    // 新容量更新爲舊容量的1.5倍
    // oldCapacity >> 1 爲按位右移一位,至關於 oldCapacity 除以2的1次冪
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 而後檢查新容量是否大於最小須要容量,若還小,就把最小須要容量看成數組的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 再檢查新容量是否超出了ArrayList 所定義的最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 若超出,則調用hugeCapacity()
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

擴容的核心就是這句:int

newCapacity = oldCapacity + (oldCapacity >> 1);

本質就是擴容 1.5 倍,並且其中使用了移位運算,這裏從計算的角度上來看,至關於 oldCapacity 除以 2 的 1 次冪(偶數除以 2 恰好除盡,奇數丟掉小數部分)。使用按位右移,效率會高不少

>> 按位右移運算符:最高位爲 0,左邊補齊 0,最高位是 1,左邊補齊 1

  • 快速計算:把 >> 左邊的數據 除以 2 的移動次冪:例如 -24 >> 2 即:-24 / 2 ^ 2 = -6

—— 此項目 【001-Java基礎知識】 章節中有具體介紹

擴容後,須要對這個新容量的範圍進行一個判斷,不能小於最小須要容量,也不能大於定義的最大容量,分狀況細細看一下(以 1 和 11 舉例,是由於這兩種都是恰好須要擴容的)

  • add 第 1 個元素的時候,數組還爲空,因此不管是 oldCapacity 仍是 newCapacity 都是 0,通過第一次判斷後,newCapacity = minCapacity 執行了,此時 newCapacity 爲 10,第二個判斷不會進入,它不可能大於數組的最大容量。
  • add 第 11 個元素的時候,oldCapacity 爲 10,newCapacity = 10 + 10/2 = 15,大於 minCapacity = 11,第一個判斷不會進入,同時它確定也沒有大於數組最大 size,不會進入 。數組容量此時就擴爲 15,add 方法中會返回一個 true,size 也增長成 11。
  • 後面都是一樣的道理 ...
3.1.2.6 hugeCapacity()

這個方法就是在 newCapacity 大於 MAX_ARRAY_SIZE 的時候,開始判斷 minCapacity 和 MAX_ARRAY_SIZE 誰大,而後賦予不一樣的值。

/**
 * 比較minCapacity和 MAX_ARRAY_SIZE
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

3.2 System.arraycopy() 和 Arrays.copyOf() 複製方法

在前面的方法中,大量的用到了這兩個方法,基本但凡涉及到元素移動的都會用到。

3.2.1 System.arraycopy()

拿 add 方法中的舉例

/**
 * 在此列表中的指定位置插入指定的元素
 * 再將從index開始以後的全部成員後移一個位置;將element插入index位置;最後size加1。
 */
public void add(int index, E element) {
    // 調用 rangeCheckForAdd 對 index 進行範圍檢查
    rangeCheckForAdd(index);
    // 保證容量足夠
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 本身複製本身,而後達到 index 以後所有元素向後挪一位的效果
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 而後將 index 賦值爲指定的元素
    elementData[index] = element;
    size++;
}

arraycopy 是 System類 中的一個方法

/**
 * 數組複製
 *     src - 源數組。
 *     srcPos - 源數組中的起始位置。
 *     dest - 目標數組。 
 *     destPos - 目的地數據中的起始位置。 
 *     length - 要複製的數組元素的數量。
 */
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

舉例:

public static void main(String[] args) {
    int[] arr = new int[10];
    arr[0] = 11;
    arr[1] = 22;
    arr[2] = 33;
    arr[3] = 44;
    arr[4] = 55;

    System.out.println("前:" + Arrays.toString(arr));
    // 指定下標後向後挪動一位
    System.arraycopy(arr, 1, arr, 2, 4);
    // 指定下標處替換元素
    arr[1] = 666;
    System.out.println("後:" + Arrays.toString(arr));
}

運行結果:

前:[11, 22, 33, 44, 55, 0, 0, 0, 0, 0]
後:[11, 666, 22, 33, 44, 55, 0, 0, 0, 0]

這樣就實現了 add 中的一個指定下標插入操做(不考慮擴容)

3.2.2 Arrays.copyOf()

因此,能夠簡單的認爲,這個方法的目的只要是爲了給原數組擴容。

public static void main(String[] args) {

    int[] arr1 = {1, 2, 3, 4, 5};
    int[] arr2 = Arrays.copyOf(arr1, 5);
    int[] arr3 = Arrays.copyOf(arr1, 10);

    System.out.println(Arrays.toString(arr1));
    System.out.println(Arrays.toString(arr2));
    System.out.println(Arrays.toString(arr3));
}

運行結果:

[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

3.3 removeAll() 和 retainAll() 中的 batchRemove() 方法

在 removeAll() 和 retainAll() 方法中,都調用了 batchRemove()方法,區別只是傳參不一樣,就能實現兩種不一樣的正反刪除效果

/**
 * 今後列表中刪除指定集合中包含的全部元素。
 */
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

/**
 * 僅保留此列表中包含在指定集合中的元素。即刪掉沒有的部分
 */
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

來重點看一下這個方法的源碼

/**
 * 刪除的具體邏輯,下面會有專題講解
 */
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[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;
}

解釋一下剛開始的那些字段

  • size :原數組長度
  • elementData: 原數組
  • modCount : 從父類繼承過來的變量,做用是記錄着集合的修改次數。

來看第一個關鍵代碼

for (; r < size; r++)
    if (c.contains(elementData[r]) == complement)
        elementData[w++] = elementData[r];

咱們以 removeAll() 爲例,意圖今後列表中刪除指定集合中包含的全部元素。即,有的就刪,沒有的就不刪。

因此 complement 通過參數傳遞過來天然是 false,因此參數指定數組中不含有原數組指定位置下標的數據的時候,就將 elementData[r] 位置的數據覆蓋掉 elementData[w++] 位置的數據,r 根據循環++自增,w 根據變量 w++ 自增,若 if 表達式不成立則,r 自增,w 不自增。

舉例:原數組:[1, 2, 3, 4, 5, 6, 7, 8, 9] ,指定參數數組: [a, b, c, 3, 5, 8, f](例子參考自)從新排版

循環次數 r w 布爾值 賦值語句 替換後的數組值 說明
1 0 0 true elementData[0]=elementData[0] [1, 2, 3, 4, 5, 6, 7, 8, 9] 1 替換 1,r++ ,w++
2 1 1 true elementData[1]=elementData[1] [1, 2, 3, 4, 5, 6, 7, 8, 9] 2 替換 2,r++ ,w++
3 2 2 false [1, 2, 3, 4, 5, 6, 7, 8, 9]
4 3 2 true elementData[2]=elementData[3] [1, 2, 4, 4, 5, 6, 7, 8, 9] 4 替換 3,r++ ,w++
5 4 3 false [1, 2, 4, 4, 5, 6, 7, 8, 9]
6 5 3 true elementData[3]=elementData[5] [1, 2, 4, 6, 5, 6, 7, 8, 9] 6 替換 4,r++ ,w++
7 6 4 true elementData[4]=elementData[6] [1, 2, 4, 6, 7, 6, 7, 8, 9] 7 替換 5,r++ ,w++
8 7 5 false [1, 2, 4, 6, 7, 6, 7, 8, 9]
9 8 5 true elementData[5]=elementData[8] [1, 2, 4, 6, 7, 9, 7, 8, 9] 9 替換 6,r++ ,w++
9 6

本身走一遍上面的邏輯,就能深入的感覺獲得

這步的做用:把須要移除的數據都替換掉,不須要移除的數據前移。(這步的處理尤其重要!)

接下來進入 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;
}

首先判斷 r 是否等於 size,若是上面的循環正常執行結束,r 和 size 應該是相同的,因此確定不會走上面,第一個 if 判斷的目的就是爲了解決某種異常狀況下(異常,併發修改)致使的 for 循環未結束,此時 r != size 因此經過 arraycopy 將添加的元素追加到w索引後面。

而第二個 if ,主要是爲了把 w 以後沒處理過的給刪掉,這樣就能夠達到目的了。

例如上面表格的例子,最後 w = 6,也就是 [1, 2, 4, 6, 7, 9, 7, 8, 9] 中從下標爲 6 的元素 7 開始刪除,將 7,8,9 賦值爲 null 後面會被 GC 清理掉。最後獲得的結果 [1, 2, 4, 6, 7, 9] 就是清除過的了 。

3.4 併發修改異常問題探索

public static void main(String[] args) {
    // 建立集合對象
    List list = new ArrayList();

    // 存儲元素
    list.add("I");
    list.add("love");
    list.add("you");

    Iterator it = list.iterator();
    while (it.hasNext()) {
        String s = (String) it.next();
        if ("love".equals(s)) {
            list.add("❤");
        }
        System.out.println(s);
    }
}

//運行結果(節選)
Exception in thread "main" java.util.ConcurrentModificationException

使用加強for或者迭代器遍歷集合的時候,若是對集合進行 list的 remove 和 add 操做,會出現 ConcurrentModificationException 併發修改異常的問題。

3.4.1 緣由解釋:

當咱們對集合進行遍歷的時候,咱們會獲取當前集合的迭代對象

//List爲例,獲取集合的迭代對象
Iterator it = list.iterator();

這個迭代對象中,封裝了迭代器的方法與集合自己的一些方法,當咱們在迭代中使用集合自己的add / remove方法的時候,就產生了ConcurrentModificationException異常,通俗的說就是,在判斷 equals 成功後,執行了 list 的 add / remove 方法, 操做集合中元素或者刪除增長了,可是迭代器不清楚,因此就報錯,若是迭代器中含有這一種方法(假設),咱們是用迭代器添加元素就不會有問題了。

詳細解釋

  • 開始時,cursor 指向下標爲 0 的元素,lastRet 指向下標爲 -1 的元素,每次調用 next 方法,cursor 和 lastRet 會分別自增 1。
  • 當忽然 ArrayList 的 remove 方法被調用(不是 Itr 的 remove),會致使被刪除元素後面的全部元素都會往前移動一位,且 modCount 這個修改次數會增長,繼續循環,去執行 next 方法,而 next 方法中首先判斷的就是 modCount 和 expectedModCount 是否相等,很明顯因爲 ArrayList 的操做,致使 modCount 變化,二者如今已經不等了,因此出現異常
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

針對這個問題,咱們給出兩個解決方案

3.4.2 解決方案:

3.4.2.1 方式1:迭代器迭代元素,迭代器修改元素

咱們假想若是Iterator迭代器中有添加或者刪除等功能就行了,但很遺憾並無,可是它的子接口 ListIterator 卻擁有 add 這個功能(ListIterator 擁有 add、set、remove 方法,Iterator 擁有 remove 方法,這裏只演示 add 方法,remove 方法就用原來的 Iterator .remove() )

ListIterator 的 add()和 Iterator 的 remove() 可使用的緣由都是由於,方法進行了添加刪除操做後,都會執行 expectedModCount = modCount 這樣的賦值操做,至關於告訴迭代器我進行了修改操做。

public static void main(String[] args) {
    // 建立集合對象
    List list = new ArrayList();
    
    // 存儲元素
    list.add("I");
    list.add("love");
    list.add("you");

    ListIterator lit = list.listIterator();
    while (lit.hasNext()) {
        String s = (String) lit.next();
        if ("love".equals(s)) {
            // add 、remove 都是能夠的
            lit.add("❤");
        }
        System.out.print(s + " ");
    }
    
    System.out.println();

    for (Object l : list){
        System.out.print(l + " ");
    }
}

//運行結果
I love you
I love ❤ you
3.4.2.1 方式2:集合遍歷元素,集合修改元素(普通for)
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Demo2 {
    public static void main(String[] args) {
        //建立集合對象
        List list = new ArrayList();

        //存儲元素
        list.add("I");
        list.add("love");
        list.add("you");

        for (int x = 0; x < list.size(); x++){
            String s = (String)list.get(x);
            if ("love".equals(s)){
                list.add("❤");
            }
            System.out.print(s + " ");
        }
    }
}

//運行結果
I love you ❤

二者都可以解決併發修改異常的問題,可是經過運行結果也能夠看出,方法一添加後,在本次遍歷中不會輸出添加的結果,而方法二卻能夠。

補充:加強for循環實現將集合進行遍歷,也產生了併發修改異常,這是由於加強for在底層也是調用的集合自己的 remove

3.4.3 iterator.remove() 的弊端

  • Iterator 只有 remove() 方法,add 方法在 ListIterator 中有
  • remove 以前必須先調用 next,remove 開始就對 lastRet 作了不能小於 0 的校驗,而l astRet 初始化值爲 -1
  • next 後只能調用一次 remove,由於 remove 會將 lastRet 從新初始化爲 -1
相關文章
相關標籤/搜索