1. 程式人生 > 實用技巧 >JDK原始碼筆記-ArrayList

JDK原始碼筆記-ArrayList

(1)構造方法

  #ArrayList()

/**
 * 預設初始化容量
 *
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 共享的空陣列物件,用於 {@link #ArrayList()} 構造方法。
 *
 * 通過使用該靜態變數,和 {@link #EMPTY_ELEMENTDATA} 區分開來,在第一次新增元素時。
 *
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 
*/ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

  ArrayList被初始化為DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個空陣列,ArrayList 考慮到節省記憶體,一些使用場景下僅僅是建立了 ArrayList 物件,實際並未使用。所以,ArrayList 優化成初始化是個空陣列,在首次新增元素時,才真正初始化為容量為 10 的陣列。

  那麼為什麼單獨聲明瞭DEFAULTCAPACITY_EMPTY_ELEMENTDATA空陣列,而不直接使用EMPTY_ELEMENTDATA呢?在下文中,我們會看到DEFAULTCAPACITY_EMPTY_ELEMENTDATA首次擴容為 10 ,而EMPTY_ELEMENTDATA按照1.5 倍擴容從 0 開始而不是 10

  #ArrayList(int initialCapacity)根據傳入的初始化容量,建立 ArrayList 陣列。如果我們在使用時,如果預先指到陣列大小,一定要使用該構造方法,可以避免陣列擴容提升效能,同時也是合理使用記憶體。

/**
 * 共享的空陣列物件。
 *
 * 在 {
@link #ArrayList(int)} 或 {@link #ArrayList(Collection)} 構造方法中, * 如果傳入的初始化大小或者集合大小為 0 時,將 {@link #elementData} 指向它。 * * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; public ArrayList(int initialCapacity) { // 初始化容量大於 0 時,建立 Object 陣列 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; // 初始化容量等於 0 時,使用 EMPTY_ELEMENTDATA 物件 } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; // 初始化容量小於 0 時,丟擲 IllegalArgumentException 異常 } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }

  比較特殊的是,如果初始化容量為 0 時,使用EMPTY_ELEMENTDATA空陣列。在新增元素的時候,會進行擴容建立需要的陣列

  #ArrayList(Collection<? extends E> c)使用傳入的c集合,作為 ArrayList 的elementData

public ArrayList(Collection<? extends E> c) {
    // 將 c 轉換成 Object 陣列
    elementData = c.toArray();
    // 如果陣列大小大於 0
    if ((size = elementData.length) != 0) {
        // defend against c.toArray (incorrectly) not returning Object[]
        // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
        // <X> 如果集合元素不是 Object[] 型別,則會建立新的 Object[] 陣列,並將 elementData 賦值到其中,最後賦值給 elementData 。
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    // 如果陣列大小等於 0 ,則使用 EMPTY_ELEMENTDATA 。
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

(2)新增單個元素

  #add(E e)方法,順序新增單個元素到陣列

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //設定到末尾並賦值
        elementData[size++] = e;
        return true;
    }

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //判斷是否是空參構造時的空陣列,如果是則確保最新有最小容量10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
        }

private void ensureExplicitCapacity(int minCapacity) {
        //增加陣列修改次數modCount,在父類 AbstractList 上,定義了modCount屬性,用於記錄陣列修改次數。
        modCount++;
        // overflow-conscious code
        //如果新增一個元素後,容量大於elementData則擴容
        if (minCapacity - elementData.length > 0)
            //擴容待會單獨拿出來分析
            grow(minCapacity);
    }

  #add(int index, E element),插入單個元素到指定位置

public void add(int index, E element) {
     //校驗是否在陣列範圍內
        rangeCheckForAdd(index);
     //校驗是否擴容
        ensureCapacityInternal(size + 1);  // Increments modCount!!

     //將index位置開始的元素往後挪
        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));
    }

(3)陣列擴容

  #grow()方法,擴容陣列,並返回它。整個的擴容過程,首先建立一個新的更大的陣列,一般是1.5 倍大小(為什麼說是一般呢,稍後我們會看到,會有一些小細節),然後將原陣列複製到新陣列中,最後返回新陣列。

private void grow(int minCapacity) {
        // 舊陣列容量
        int oldCapacity = elementData.length;
        //新陣列容量 1.5倍   
        //oldCapacity >> 1 向右位運算  例如:4 二進位制 100 >> 1 = 10 變成 2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //確保擴容最小為1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //MAX_ARRAY_SIZE = 
        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);
    }

private static int hugeCapacity(int minCapacity) {
        
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //四捨五入,保證不超過int的最大值
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

(4)新增多個元素

  #addAll(Collection<? extends E> c)方法,批量新增多個元素。在我們明確知道會新增多個元素時,推薦使用該該方法而不是新增單個元素,避免可能多次擴容

public boolean addAll(Collection<? extends E> c) {
        //將c轉成陣列
        Object[] a = c.toArray();
        //c的長度
        int numNew = a.length;
         //校驗新增這些元素需不需要擴容,保證擴容最小1.5倍
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //將c中的元素複製到elementData從size位置開始
        System.arraycopy(a, 0, elementData, size, numNew);
        //陣列大小增加c的長度
        size += numNew;
        return numNew != 0;
    }

(5)從指定位置插入多個元素

  #addAll(int index, Collection<? extends E> c)方法,從指定位置開始插入多個元素

public boolean addAll(int index, Collection<? extends E> c) {
        //校驗插入位置是否在陣列內
        rangeCheckForAdd(index);
        //將想要新增的元素轉為陣列
        Object[] a = c.toArray();
        //拿到想要新增元素的長度
        int numNew = a.length;
        //校驗是否需要擴容,保證最小擴容1.5倍
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //拿到插入位置後的elementData中元素的長度
        int numMoved = size - index;
        //插入位置不是在elementData末尾
        if (numMoved > 0)
            //複製,將插入位置後的元素,全部後挪到能夠容納插入新元素的位置
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //將插入的新元素複製進指定位置
        System.arraycopy(a, 0, elementData, index, numNew);
        //增加陣列長度
        size += numNew;
        return numNew != 0;
    }

(6)移除單個元素

  #remove(int index)方法,移除指定位置的元素,並返回該位置的原元素

public E remove(int index) {
        //檢查插入位置是不是在陣列內
        rangeCheck(index);
        //運算元+1
        modCount++;
        //拿到elementData中索引位置的元素
        E oldValue = elementData(index);
        //拿到索引位置後元素的數量,這裡注意size是從1開始的,index是從0開始的
        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; }

  #remove(Object o)方法,移除首個為o的元素,並返回是否移除到

public boolean remove(Object o) {
        //如果刪除元素是null
        if (o == null) {
            //迴圈遍歷
            for (int index = 0; index < size; index++)
                //如果等於null
                if (elementData[index] == null) {
                    //刪除
                    fastRemove(index);
                    return true;
                }
        } else {
            //與上述同理
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

private void fastRemove(int index) {
        //運算元+1
        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
    }

(7)移除多個元素

  #removeRange(int fromIndex, int toIndex)方法,批量移除[fromIndex, toIndex)的多個元素,注意不包括toIndex的元素

protected void removeRange(int fromIndex, int toIndex) {
        //運算元+1
        modCount++;
        //toIndex後的元素數量,也就是需要移動的元素數量
        int numMoved = size - toIndex;
         //toIndex後的元素前挪
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        //元素前挪後,將後邊的位置置null
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

(8)批量移除指定的多個元素

  #removeAll(Collection<?> c)方法,移除在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和w的意思,r其實是遍歷elementData的索引,w是elementData中的c有的元素,就是需要刪除的元素的索引
        int r = 0, w = 0;
        boolean modified = false;
        try {
            //迴圈遍歷原陣列
            for (; r < size; r++)
                //如果elementData當前元素c中有,那麼覆蓋w位置的元素
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            //異常處理 將r位置之後的的元素寫入到w位置之後,保證不多出來
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            //因為在之前就將需要剩下的元素前挪了,所以將w位置之後的元素置null
            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;
    }

  #retainAll(Collection<?> c)elementData陣列和指定多個元素的交集,刪除不在c中的元素,因為邏輯與removeRange相同,所以略過

(9)查詢單個元素

  #indexOf(Object o)方法,查詢首個為指定元素的位置

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;
    }

  #contains(Object o)方法,就是基於該方法實現

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

  有時我們需要查詢最後一個為指定元素的位置,所以會使用到#lastIndexOf(Object o)方法

public int lastIndexOf(Object o) {
        //反向遍歷
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

(10)獲取指定位置的元素

  #get(int index)方法,獲得指定位置的元素

public E get(int index) {
        //校驗索引是否越界
        rangeCheck(index);
        
        return elementData(index);
    }

E elementData(int index) {
        //返回指定索引位置的元素
        return (E) elementData[index];
    }

(11)設定指定位置的元素

  #set(int index, E element)方法,設定指定位置的元素

public E set(int index, E element) {
        //越界校驗
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

(12)轉換成陣列

  #toArray()方法,將 ArrayList 轉換成[]陣列

public Object[] toArray() {
        //注意返回的是Object型別的陣列
        return Arrays.copyOf(elementData, size);
    }

  實際場景下,我們可能想要指定T泛型的陣列,那麼我們就需要使用到#toArray(T[] a)方法

public <T> T[] toArray(T[] a) {
        //如果a比elementData小,直接複製一個數組返回
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        //如果elementData比a小,則將elementData複製到a中
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

(13)判斷相等

  #equals(Object o)方法,判斷是否相等

public boolean equals(Object o) {
    // 如果是自己,直接返回相等
    if (o == this) {
        return true;
    }

    // 如果不為 List 型別,直接不相等
    if (!(o instanceof List)) {
        return false;
    }

    // 獲得當前的陣列修改次數
    final int expectedModCount = modCount;
    // ArrayList can be subclassed and given arbitrary behavior, but we can
    // still deal with the common case where o is ArrayList precisely
    // <X> 根據不同型別,呼叫不同比對的方法。主要考慮 ArrayList 可以直接使用其 elementData 屬性,效能更優。
    boolean equal = (o.getClass() == ArrayList.class)
        ? equalsArrayList((ArrayList<?>) o)
        : equalsRange((List<?>) o, 0, size);

    // 如果修改次數發生改變,則丟擲 ConcurrentModificationException 異常
    checkForComodification(expectedModCount);
    return equal;
}

  選擇兩種比較方法的原因:普通的list只能使用迭代器,相比與ArrayList的elementData遍歷,效能會略低一些

boolean equalsRange(List<?> other, int from, int to) {
    // 如果 to 大於 es 大小,說明說明發生改變,丟擲 ConcurrentModificationException 異常
    final Object[] es = elementData;
    if (to > es.length) {
        throw new ConcurrentModificationException();
    }
    // 通過迭代器遍歷 other ,然後逐個元素對比
    var oit = other.iterator();
    for (; from < to; from++) {
        // 如果 oit 沒有下一個,或者元素不相等,返回 false 不匹配
        if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) {
            return false;
        }
    }
    // 通過 oit 是否遍歷完。實現大小是否相等的效果。
    return !oit.hasNext();
}

private boolean equalsArrayList(ArrayList<?> other) {
    // 獲得 other 陣列修改次數
    final int otherModCount = other.modCount;
    final int s = size;
    boolean equal;
    // 判斷陣列大小是否相等
    if (equal = (s == other.size)) {
        final Object[] otherEs = other.elementData;
        final Object[] es = elementData;
        // 如果 s 大於 es 或者 otherEs 的長度,說明發生改變,丟擲 ConcurrentModificationException 異常
        if (s > es.length || s > otherEs.length) {
            throw new ConcurrentModificationException();
        }
        // 遍歷,逐個比較每個元素是否相等
        for (int i = 0; i < s; i++) {
            if (!Objects.equals(es[i], otherEs[i])) {
                equal = false;
                break; // 如果不相等,則 break
            }
        }
    }
    // 如果 other 修改次數發生改變,則丟擲 ConcurrentModificationException 異常
    other.checkForComodification(otherModCount);
    return equal;
}

(14)清空陣列

public void clear() {
        modCount++;

        // clear to let GC do its work
        //遍歷置null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

(15)克隆

  #clone()方法,克隆 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);
        }
    }

(16)建立子陣列

  #subList(int fromIndex, int toIndex)方法,建立 ArrayList 的子陣列

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

  程式碼並不複雜,需要注意的是,SubList 不是一個只讀陣列,而是和根陣列root共享相同的elementData陣列,只是說限制了[fromIndex, toIndex)的範圍

(17)建立Iterator迭代器

  #iterator()方法,建立迭代器。一般情況下,我們使用迭代器遍歷 ArrayList、LinkedList 等等 List 的實現類

public Iterator<E> iterator() {
        return new Itr();
    }

  建立 Itr 迭代器。Itr 實現java.util.Iterator介面,是 ArrayList 的內部類。雖然說 AbstractList 也提供了一個 Itr 的實現,但是 ArrayList 為了更好的效能,所以自己實現了,在其類上也有註釋“An optimized version of AbstractList.Itr”

  Itr一共有三個屬性:

/**
 * 下一個訪問元素的位置,從下標 0 開始。
 */
int cursor;       // index of next element to return
/**
 * 上一次訪問元素的位置。
 *
 * 1. 初始化為 -1 ,表示無上一個訪問的元素
 * 2. 遍歷到下一個元素時,lastRet 會指向當前元素,而 cursor 會指向下一個元素。這樣,如果我們要實現 remove 方法,移除當前元素,就可以實現了。
 * 3. 移除元素時,設定為 -1 ,表示最後訪問的元素不存在了,都被移除咧。
 */
int lastRet = -1; // index of last element returned; -1 if no such
/**
 * 建立迭代器時,陣列修改次數。
 *
 * 在迭代過程中,如果陣列發生了變化,會丟擲 ConcurrentModificationException 異常。
 */
int expectedModCount = modCount;

// prevent creating a synthetic constructor
Itr() {}

  Itr對Iterator的四個實現方法:

  #hasNext()方法,判斷是否還可以繼續迭代

public boolean hasNext() {
            //cursor = size,說明到頭了
            return cursor != size;
        }

  #next()方法,下一個元素

public E next() {
            //檢查運算元,判斷陣列是否發生了變化
            checkForComodification();
            //超出size,丟擲異常
            //i記錄當前cursor位置
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            //判斷如果超出elementData大小,則說明被修改,丟擲異常
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //cursor指向下一個位置
            cursor = i + 1;
            //返回當前位置的元素,且lastRet指向當前位置
            return (E) elementData[lastRet = i];
        }    

  #remove()方法,移除當前元素

public void remove() {
            //lastRet小於0,則沒有指向任何元素,丟擲異常
            if (lastRet < 0)
                throw new IllegalStateException();
            //檢查陣列是否發生變化
            checkForComodification();

            try {
                //移除lastRet位置的元素
                ArrayList.this.remove(lastRet);
                //cursor指向lastRet位置,因為被移除了,需要後退
                cursor = lastRet;
                //因為元素被移除,則lastRet不指向任何位置
                lastRet = -1;
                //記錄新的陣列修改數
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

  #forEachRemaining(Consumer<? super E> action)方法,消費剩餘未迭代的元素

public void forEachRemaining(Consumer<? super E> consumer) {
            //判斷是否為null,為null丟擲異常
            Objects.requireNonNull(consumer);
            //獲取當前陣列大小
            final int size = ArrayList.this.size;
            //將i指向cursor
            int i = cursor;
            //如果大於size,則沒有需要消費的元素
            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、lastRet的指向
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }