1. 程式人生 > 程式設計 >ArrayList原始碼解析

ArrayList原始碼解析

1. 概述

ArrayList底層通過陣列實現,是執行緒不安全的,具有隨機訪問快(根據下標),隨機增刪慢的特點

ArrayList繼承AbstractList抽象類,實現List介面

2. 成員變數

    /**
     * 預設初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用於空物件的共享空陣列
     * 用於建立指定長度為0的ArrayList的構造方法
     * java.util.ArrayList#ArrayList(int)
     * java.util.ArrayList#ArrayList(java.util.Collection)
     *
     */
private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 用於預設大小的空物件的共享空陣列(跟上面的區分開來) * 用於建立預設長度為10的ArrayList的構造方法 * java.util.ArrayList#ArrayList() * */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 儲存內容的陣列 */ transient
Object[] elementData; // non-private to simplify nested class access /** * ArrayList的長度 */ private int size; /** * 陣列的最大長度 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 複製程式碼

這裡有一點要特別注意: elementData.length是指陣列的長度 size是指List的長度 這兩個長度不是一樣的

3. 建構函式

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            // 初始容量大於0時,新建Object陣列指向elementData
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 初始容量等於0時,EMPTY_ELEMENTDATA陣列指向elementData
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            // 初始容量小於0,丟擲異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList() {
        // 無參構造,elementData指向預設的空陣列
        // 這也表明,elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的話
        // 表示ArrayList還沒有新增過任何元素
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        // collection轉陣列
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 陣列長度不等於0
            // [2] c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                // [1]
                elementData = Arrays.copyOf(elementData,size,Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
複製程式碼

[1] 處ArrayList的原始碼中,涉及到陣列複製的,都是使用Arrays提供的工具類

[2] 處關於toArray方法不一定返回Object陣列,可以看看這篇文章 c.toArray might not return Object[]?

如果是涉及到陣列的移動(有新增、刪除引發),會使用java.lang.System#arraycopy方法

4. 常用的方法

4.1 新增

方法執行流程

可以看到addaddAll方法都會使用到ensureCapacityInternal,該方法中的核心程式碼是對ensureExplicitCapacity方法的呼叫

ensureExplicitCapacity有兩點作用:

  1. 增加ArrayList的修改次數modCount
  2. 容量不夠的情況下,執行grow方法,進行擴容操作

modCount記錄了物件被修改的次數

java.util.ArrayList#add(E)

    public boolean add(E e) {
        /*
        1. 確保elementData陣列擁有足夠的容量
        2. 增加修改次數
         */
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 陣列新增元素
        elementData[size++] = e;
        return true;
    }
    /**
     * 確保陣列擁有足夠的容量
     * @param minCapacity 最小需要的容量
     */
    private void ensureCapacityInternal(int minCapacity) {

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 表示elementDat還沒有新增過元素
            minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        // 增加修改次數
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            // 當最小需要的容量超出了陣列的長度,則需要擴容
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 擴容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 擴容後還是小於需要的最小容量,則使用minCapacity作為newCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 擴容後發現newCapacity大於最大的陣列長度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 通過Arrays工具類進行陣列擴容
        // minCapacity is usually close to size,so this is a win:
        elementData = Arrays.copyOf(elementData,newCapacity);
    }
複製程式碼

java.util.ArrayList#add(int,E)

    public void add(int index,E element) {
        // 檢查index有沒有越界
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 將elementData[index:]的元素移動到elementData[index+1:]
        System.arraycopy(elementData,index,elementData,index + 1,size - index);
        // 在index位置新增元素
        elementData[index] = element;
        size++;
    }
    
    private void rangeCheckForAdd(int index) {
        // [1]
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
複製程式碼
  • [1]處判斷是否越界不是根據elementData的長度,而是根據ArrayList的長度size去判斷

java.util.ArrayList#addAll(java.util.Collection<? extends E>)

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a,0,numNew);
        size += numNew;
        return numNew != 0;
    }
複製程式碼

方法很簡單,自己look look就行了

4.2 刪除

java.util.ArrayList#remove(int)

    public E remove(int index) {
        // 檢查是否越界
        rangeCheck(index);

        modCount++;
        // 根據index找到對應的value
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 將elementData[index+1:]移動到elementData[index:]
            System.arraycopy(elementData,index+1,numMoved);
        
        // 清空最後一個元素
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
複製程式碼

java.util.ArrayList#remove(java.lang.Object)

    public boolean remove(Object o) {
        if (o == null) {
        // 如果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) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData,numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
複製程式碼

其他刪除的方法的實現都不負責,有興趣可以自己挑來看看

這裡可以看到,很多方法裡面,關於資料的移動,都是通過System.arraycopy實現

4.3 查詢

    public E get(int index) {
        // 檢查陣列越界
        rangeCheck(index);

        return elementData(index);
    }
複製程式碼

方法很簡單,自己look look

5. 內部類

SubList

ArrayList有個subList的方法,這個方法返回的是ArrayList的一個檢視

    public List<E> subList(int fromIndex,int toIndex) {
        subListRangeCheck(fromIndex,toIndex,size);
        return new SubList(this,fromIndex,toIndex);
    }
複製程式碼

方法返回的是SubList類的物件

可以看到這個類把ArrayList的增刪改查方法基本都自己實現了一遍

先來看看構造方法

        SubList(AbstractList<E> parent,int offset,int fromIndex,int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            // [1]
            this.modCount = ArrayList.this.modCount;
        }
複製程式碼

可以看到[1]處,在建立SubList物件的時候,會把當時ArrayList.modCount的值賦值給SubList.modCount,Sublist的modCount的用處體現在checkForComodification方法中(下文會提及)

再來看看增刪改查的流程

可以看到增刪改查的方法都會先呼叫檢查邊界的方法rangeCheckrangeCheckForAdd,然後呼叫checkForComodification

checkForComodification方法的作用是檢查SubList物件的modCount跟當前ArrayList的modCount是否相等

        private void rangeCheck(int index) {
            if (index < 0 || index >= this.size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

        private void rangeCheckForAdd(int index) {
            if (index < 0 || index > this.size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        
        private void checkForComodification() {
        // [1]
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }
複製程式碼

可以看到[1]處,當檢查modCount不相等的時候,表示這時還有其他物件在修改ArrayList,就會丟擲ConcurrentModificationException

modCountItr內部類中也起著相同的作用

現在來看看SubList增刪改查的具體是現實

        public void add(int index,E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            // 實際呼叫的是ArrayList的add方法
            parent.add(parentOffset + index,e);
            // 同步一下modCount的資訊
            this.modCount = parent.modCount;
            this.size++;
        }
        
        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            // 實際呼叫的是ArrayList的remove方法
            E result = parent.remove(parentOffset + index);
            // 同步一下modCount
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }
        
        public E set(int index,E e) {
            rangeCheck(index);
            checkForComodification();
            // 呼叫ArrayList的elementData方法
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }
        
        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            // 呼叫ArrayList的elementData方法
            return ArrayList.this.elementData(offset + index);
        }
複製程式碼

正如上面所提及的,SubList對增刪改查的實現就是檢查邊界 + 比較modCount + 呼叫ArrayList本身對應的方法

Itr

在ArrayList中會有iterator方法,方法具體實現如下

    public Iterator<E> iterator() {
        return new Itr();
    }
複製程式碼

可以看到方法是直接返回一個新建的Iter類物件

Itr內部類實現了Iterator介面,來看看它的成員變數

        int cursor;       // 下一個元素的下標
        int lastRet = -1; // 最後一次返回的元素的下標; -1 if no such
        int expectedModCount = modCount; // [1]期望的修改次數
複製程式碼

可以從[1]處看到,Itr物件也會使用modCount,實際作用也是檢查當前有沒有別的物件在修改ArrayList,如果有就拋ConcurrentModificationException異常

接下來看看常用方法的實現

        public boolean hasNext() {
            return cursor != size;
        }
        
        public E next() {
            // 檢查modCount
            checkForComodification();
            int i = cursor;
            // 檢查有沒有超出size
            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();
            }
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
複製程式碼

方法還是比較容易讀懂的,可以自己看看

6. 總結

ArrayList是底層基於陣列實現的常用的集合類,相較於陣列,ArrayList內部實現了關於陣列越界、擴容相關的檢查,使用起來比資料要簡單,當然,陣列的效率肯定會比ArrayList要高

7. 推薦閱讀

為什麼阿里巴巴要求謹慎使用ArrayList中的subList方法

為什麼阿里巴巴建議集合初始化時,指定集合容量大小

為什麼阿里巴巴禁止在 foreach 迴圈裡進行元素的 remove/add 操作