Java 8 ArrayList 原始碼簡單分析

不呆不呆的小sa瓜發表於2020-03-09

前言

本人從學校畢業也快三年了,加上學校工作室和實習也算工作了快五年的碼農了。
平時比較喜歡專研一些新的技術或者是主流技術,前兩年是什麼都想學,導致接觸面廣,但是在深度上有些欠缺。
所以後來思考一番後,覺得需要專注某一個領域,萬金油很多,某個領域專業型的人才還是不多。
不知道我能否達到那個層次,但是我還是想那方向去努力。

雖然之前會去閱讀原始碼和 Spring 官方文件,但是都是一掃而過,沒有記錄下來。
倒是記錄了一些中介軟體伺服器部署的操作過程。
這還是第一次如此比較深入的分析原始碼,也是第一次寫出來。
各位發現有錯的或者寫的不好的,歡迎指出來,共同學習共同成長。
複製程式碼

extends And implements

繼承 AbstractList 實現集合屬性和基本的操作

實現 List 介面,覆蓋掉 AbstractList 中的一些實現

實現 RandomAccess 介面,實現此介面有隨機讀寫功能

實現 Cloneable 介面,克隆功能

實現 java.io.Serializable 介面,序列化

初始化filed

private static long serialVersionUID; 序列化id,保證反序列化也是此類

private static final int DEFAULT_CAPACITY = 10; 預設的陣列長度

private static final Object[] EMPTY_ELEMENTDATA = {}; 空的陣列例項

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 空的陣列例項

EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 的區別 在於操作 elementData 的時候知道物件是有參構造方法還是無參構造方法初始化的

DEFAULTCAPACITY_EMPTY_ELEMENTDATA 標記的 elementData 會使用 DEFAULT_CAPACITY 變數進行初始化,或者擴容判斷操作

transient Object[] elementData; 儲存元素的例項陣列

如果是使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 初始化例項陣列,則使用 DEFAULT_CAPACITY 預設值來初始化陣列大小

private int size; ArrayList 的大小( ArrayList 包含的元素的數量)

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 要分配的陣列的最大大小,分配過大的陣列大小可能會導致 OutOfMemoryError

注意:這裡的所有陣列都是 Object 型別的
複製程式碼

構造方法

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
複製程式碼
  • 無參構造方法 elementData 用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是標記作用,主要是為了能夠用到 DEFAULT_CAPACITY 值。下面會講到
    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);
        }
    }
複製程式碼
  • 有參構造方法,initialCapacity == 0 時,就會用 EMPTY_ELEMENTDATA 初始化 elementData 。EMPTY_ELEMENTDATA 作為標記,認為 ArrayList 物件是通過有參構造方法例項化
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
複製程式碼
  • c.toArray() 是複製出來的物件。
  • 這裡有一個轉換,length != 0 將 elementData 轉為 Object[],同樣通過有參構造方法例項化 ArrayList ,如果 length == 0 使用 EMPTY_ELEMENTDATA 賦值標記

ArrayList 函式

trimToSize

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
複製程式碼
  • 聽名字就知道是縮減 size,和 String.trim() 類似,這裡是將 elementData 長度縮小到最小有效長度。
  • modCount 是用來記錄被操作次數,EMPTY_ELEMENTDATA 就和上面提到的是作為標記

ensureCapacity

    // 確保 ArrayList 能夠容納所儲存的資料
    // 這個方法目前只在 com.sun.corba.se.impl.orbutil.DenseIntMapImpl 看到有呼叫 
    // 就是這一行 list.ensureCapacity( index + 1 );
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // 其他的則可以直接通過 minCapacity > minExpand 條件
            ? 0
            // DEFAULTCAPACITY_EMPTY_ELEMENTDATA 標記的 elementData
            // 將使用預設大小進行初始化
            : DEFAULT_CAPACITY;

        // 最小容量大於最小擴充套件
        //(除非是當前 elementData 是使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 標記,說明它現在還是空的陣列)
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // minCapacity 需要承載資料的容量的大小 > 當前陣列大小,則進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    // 擴容
    // 根據入參方向看出 minCapacity 通常接近於 size
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 將容量擴大為 <= 1.5 倍
        // 簡單的例子,如果當前二進位制容量是 1100 >> 1 為 0110,是為原來的 1/2
        // 但是如果二進位制的最後一位為1, 1101 >> 1 = 0110, 最後一位丟失,所以會比原來的 1/2 小
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // oldCapacity 已經很大的時候,再擴大就會超出有符號int的最大正數值,最左位變成 1 就為負數
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 最大承載值矯正
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        // 最大隻能達到 Integer.MAX_VALUE
        // 也就是 0x7FFFFFFF, 二進位制為 ‭0111 1111 1111 1111 1111 1111 1111 1111,有符號的最大int值
        // MAX_ARRAY_SIZE 上面有列出來 Integer.MAX_VALUE - 8
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
複製程式碼
  • 以上是 ArrayList 擴容的過程

size and isEmpty

    // 返回當前陣列元素個數
    // size 是使用基本資料型別定義的,預設值是 0
    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }
複製程式碼

contains

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

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

    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;
    }
複製程式碼
  • 這裡是會返回 -1的,使用的時候要注意,lastIndexOf 和 indexOf 剛好便利的順序是相反的

copy

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // 正常來說這個是不會發生的,只有可能是底層 copy 時出現問題了
            throw new InternalError(e);
        }
    }

    // copyOf 使用的 System.arraycopy 進行陣列複製
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // 注意這裡,使用 Arrays.copyOf 
            // a.length < size 時 Arrays.copy 會新建一個陣列物件
            // 所以這裡返回的物件不是原來的 a, 且會丟棄掉大於 a.length 之後的資料
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        // 這裡返回的是 a
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
複製程式碼
  • 以上是 ArrayList 複製資料的過程

get

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    // 這裡並沒有判斷 index < 0 的情況
    // 因為在 elementData() 方法中如果 index 是 < 0 會丟擲 ArrayIndexOutOfBoundsException
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    E elementData(int index) {
        return (E) elementData[index];
    }
複製程式碼

ArrayList 資料操作

set

// set

    // 簡單的陣列操作,返回原來這個坑位的元素
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
複製程式碼

add

// add

    public boolean add(E e) {
        // 先進行擴容操作,如果有操作會 增加 modCount
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        // 先進行擴容操作,如果有操作會 增加 modCount
        ensureCapacityInternal(size + 1);
        // 將陣列從 index 位置開始後移,將 index 這個坑空出來
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    // addAll 過載方法
    public boolean addAll(Collection<? extends E> c) {
        // 記住,toArray 時copy不同的物件的,所以 c 改變它的 elementData 坑的指向,是不會影響到當前 list 的
        Object[] a = c.toArray();
        // 被加進來的集合的長度
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    // addAll 過載方法,此方法就比 addAll(Collection) 多了一步,從 index 後移 c.size 位,就是將 size - index 個元素往後移 c.size 位
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);

        int numMoved = size - index;
        if (numMoved > 0)
            // 從 index 到 size 有 numMoved 個元素,複製到新的陣列,新陣列從 index + c.size 開始插入
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

    // 這裡對 index 進行 < 0 判斷是因為 add(int, E) 方法會先進行陣列複製,如果 index < 0 會有 ArrayIndexOutOfBoundsException,導致陣列複製失敗
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private void ensureCapacityInternal(int minCapacity) {
        // minCapacity 是認為當前陣列最小能夠承載資料的最小值
        // 呼叫 add() 方法,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 標記的 elementData 會使用 DEFAULT_CAPACITY 預設值來進行擴容
        // 這裡為什麼要用 Math.max 得到最大值呢,都已經是一個空陣列了,minCapacity 應該會 < DEFAULT_CAPACITY.
        
        // 因為當前這個方法在 addAll() 也有呼叫,this.elementData 被 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 標記
        // 但是 addAll() 傳入的物件 length 是有可能 > DEFAULT_CAPACITY
        
        // 非 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 標記的 elementData 是通過有參構造方法初始化的
        // 有初始容量,所以不使用 DEFAULT_CAPACITY
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 擴容
        ensureExplicitCapacity(minCapacity);
    }
複製程式碼

remove

// remove

    // remove 有兩個過載方法,引數為 int 返回的是被刪除的物件
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        // 需要移動坑的元素個數
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // index 這個坑位後面的元素全部往前移
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 置為 null 可以釋放記憶體空間,GC可以回收這個佔著茅坑的物件
        elementData[--size] = null;

        return oldValue;
    }

    // remove 有兩個過載方法,引數為 Object 返回的是 boolean 型別
    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++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    // 刪除本集合中存在 c 集合中的元素
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

    // 保留本集合中存在 c 集合中的元素
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

    // complement 決定是刪除還是保留
    private boolean batchRemove(Collection<?> c, boolean complement) {
        // 定義區域性陣列可以減少本物件的定址
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        // 標記是否發生改變
        boolean modified = false;
        try {
            // 這裡將需要的元素保留,並且向前移位,r 位移動到 w 位, 這裡 r >= w
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            // 原始碼的註釋是:保持與AbstractCollection的行為相容性,即使c.contains()丟擲

            // 我的看法是會出現多個執行緒操作的情況,導致 r != size
            // 這裡有一個問題,結合上面來看
            // 如果在進行移位的時候,ArrayList remove 了元素
            // 那麼上面的迴圈裡面就會報 ArrayIndexOutOfBoundsException 錯誤
            // 所以這裡的 r == size 的
            
            // 如果在進行移位的時候,ArrayList add 元素
            // 此時elementData 和 size 都是在同步變化的
            // 所以要想進入這個判斷裡面執行,就是在上面迴圈移位結束之後
            // 在進入 if 判斷之前,ArrayList 發生了 add 操作
            // 最終 r < size
            if (r != size) {
                // 從 r 開始
                // 就是原來的 size 位開始往前移動 size - r 個 add 的元素
                // 目標陣列從上面保留元素的最大位 w 開始寫入,移動 size - r 個元素
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                // w 則增加 size - r 個 add 的元素
                w += size - r;
            }
            // w == size 表示沒有需要去除的元素
            if (w != size) {
                for (int i = w; i < size; i++)
                    // 讓 JVM GC 掉這個坑,釋放記憶體
                    elementData[i] = null;
                // 改變了 size - w 次
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

    // fastRemove(int) 和 remove(int) 功能是差不多的
    // 只是 fastRemove(int) 沒有返回型別
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 同樣的,置為 null 可以釋放記憶體空間,GC可以回收這個佔著茅坑的物件
        elementData[--size] = null;
    }
複製程式碼

clear

// clear

    public void clear() {
        modCount++;

        // 這裡也是,置為 null 可以釋放記憶體空間,GC可以回收這個佔著茅坑的物件
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        // 並把size置為 0 ,此時 isEmpty 就是 true
        size = 0;
    }
複製程式碼
  • 以上是 ArrayList 對資料進行增刪改的過程
  • modCount 主要是用於判斷當前陣列被資料操作時,沒有其他的執行緒同時操作,具體到下面來講

Iterator

  • 討論 ArrayList 中的 Iterator 和 modCount 用途
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        // 遊標,當前位置
        int cursor;
        // 最後一個返回元素的索引;-1表示沒有
        int lastRet = -1;
        // 預計 modCount,用來標記操作
        int expectedModCount = modCount;

        // 這個應該不用說了吧,大家都知道,嘿嘿
        public boolean hasNext() {
            return cursor != size;
        }

        public E next() {
            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];
        }

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

        // 如果 modCount != expectedModCount
        // 在迭代的時候其他的執行緒也修改了 elementData
        // 可能會導致陣列越界問題
        // 如果發成了排序,也會造成併發修改異常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

複製程式碼
  • ArrayList 的迭代器實現還是比較簡單的,主要就是陣列的遍歷,使用 modCount 控制在迭代期間多執行緒操作
  • 最後看看Java 8 新增的 ArrayList forEach() 和 ArrayList 迭代器中的 forEachRemaining() 的區別

forEach and forEachRemaining

// Arraylist.forEach()
    @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();
        }
    }

// ArrayList.Itr.forEachRemaining()
        @Override
        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++]);
            }
            // 在迭代結束時更新一次,以減少堆寫流量
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
複製程式碼
  • forEach() 是 Iterable 引入的 default 方法,forEachRemaining() 是 Iterator 引入的 default 方法
  • forEach() 是從 i = 0 開始進行 for 迴圈遍歷的
  • forEachRemaining() 可以是使用 while 迴圈遍歷,結合 iterator 迭代中的 cursor 遊標,對從遊標位置開始的元素進行遍歷操作

總結

以上是我對 ArrayList 常用的一些方法和內部操作進行的分析

簡單的分析了 ArrayList 擴容和刪除操作,還要一些操作對 elementData 物件的變更

ArrayList 基於陣列的資料結構形式,可快速讀取,但是在刪除和寫入上需要複雜的判斷會對速度一定影響,
看完分析可以看出,它並不是一個執行緒安全的集合

ArrayList set() 和 add() 、addAll() 是沒有對 null 過濾的,可以儲存 null

在以上貼出的程式碼中,equals() 是 Object 提供的。
ArrayList 沒有重寫 equals() 和 hashCode(),使用的是 java.util.AbstractList 中的
複製程式碼

相關文章