集合類源碼解析——ArrayList

類圖

成員變量

  • DEFAULT_CAPACITYjava

    默認初始化容量,表明 elementData 數組的長度面試

  • EMPTY_ELEMENTDATA數組

    構造空 ArrayList 實例時,賦值給 elementData 的空數組實例app

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA函數

    構造默認容量 ArrayList 實例時,賦值給 elementData 的空數組實例ui

  • elementDatathis

    ArrayList 實際存儲元素的動態數組spa

  • size設計

    ArrayList 中實際容納元素的個數3d

構造函數

public ArrayList(int initialCapacity)

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //當賦值給 ArrayList 的容量爲 0 時,令 elementData = EMPTY_ELEMENTDATA; 
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }
}
複製代碼

public ArrayList()

public ArrayList() {
    	//當構造默認容量的 ArrayList 時,令 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
複製代碼

public ArrayList(Collection<? extends E> c)

public ArrayList(Collection<? extends E> c) {
    	// 將源集合的數據轉化爲 array 賦值給 elementData
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class) {
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
}
複製代碼

重要方法

add(E e)

public boolean add(E e) {
        // 保證添加元素後, ArrayList 不溢出
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
複製代碼

該方法調用了 ensureCapacityInternal 保證擴容後不會溢出。

ensureCapacityInternal(int minCapacity)

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
複製代碼

calculateCapacity(Object[] elementData, int minCapacity)

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}
複製代碼

該方法會返回添加元素後最小的 capacity 的值。若是初始化 ArrayList 時未指定容量,那麼這個最小值將會是 DEFAULT_CAPACITY

ensureExplicitCapacity(int minCapacity)

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

        if (minCapacity - elementData.length > 0) {
            //若是添加元素後,數組的容量會大於現有數組的 length。須要將數組擴容
            grow(minCapacity);
        }
}
複製代碼

這裏維護了 modCount 的改變。

grow(int minCapacity)

private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 1. 當 ArrayList 第一次放入 元素時,擴容後新的數組容量爲 1 或者 10;
        // 2. 當 ArrayList 添加元素後容量大於 MAX_ARRAY_SIZE 時,容量會變爲 MAX_ARRAY_SIZE 或 Integer.MAX_VALUE
        // 3. 其它狀況下,擴容操做使數組容量變爲原來的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        // 若新增元素後,oldCapacity * 1.5 > MAX_ARRAY_SIZE,調用 hugeCapacity 方法計算容量
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            newCapacity = hugeCapacity(minCapacity);
        }
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
複製代碼

這裏看到 ArrayList 擴容後新數組的容量有三種狀況。因此面試被問到 ArrayList 擴容後容量怎樣變化時,最好將三種狀況都說出來,而不僅是回答變爲原容量的 1.5 倍。

add(int index, E element)

public void add(int index, E element) {
        // add 方法的索引範圍校驗
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);
        // 調用 System.arraycopy 將數組索引不小於 index 的元素後移
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        elementData[index] = element;
        size++;
    }
複製代碼

remove(int index)

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

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

        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);
        }
        // 將最後一位元素置空,便於 GC 回收
        elementData[--size] = null;

        return oldValue;
    }
複製代碼

remove(Object o)

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;
    }
複製代碼

分爲空元素和非空元素兩種狀況遍歷。

刪除 ArrayList 中出現的第一個等於 o 的元素,查找到時,調用 fastRemove 方法刪除。

fastRemove 方法移除了索引越界檢查以及返回值的保存。

removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
複製代碼

調用批量刪除方法 batchRemove,注意這裏傳入的布爾類型參數值爲 false

batchRemove(Collection<?> c, boolean complement)

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        // r 表示 elementData 中待讀取的下一個數據對應的索引;w 表示待寫入的下一個索引
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++) {
                // 若是是刪除集合中的元素,則將未出如今 c 中的元素寫入到索引爲 w 的位置;不然將出如今 c 中的元素寫入到索引爲 w 的位置。
                if (c.contains(elementData[r]) == complement) {
                    elementData[w++] = elementData[r];
                }
            }
        } finally {
            // 保留與 AbstractCollection 行爲的兼容性
            if (r != size) {
                System.arraycopy(elementData, r,
                        elementData, w,
                        size - r);
                w += size - r;
            }
            if (w != size) {
                // 將已寫入元素以後的元素置爲 null
                for (int i = w; i < size; i++) {
                    elementData[i] = null;
                }
                // modCount 增長次數爲 刪除的元素個數
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
複製代碼

該方法設計的很巧妙。

  1. 設置了 rw 兩個變量來控制數組的已讀標記和寫入標記。r 表示已經遍歷過的數據的索引,w 表示已寫入的數據的索引。(我的感受相似於 NettyByteBuf 的設計)

  2. 經過 complement 變量來控制寫入的數據是不是集合 c 中出現過的數據。若是 complement == true,則寫入的元素爲該 ArrayList 實例 與集合 c 中共同存在的元素;若是 complement == false,則寫入的元素爲該實例元素去除集合 c 中元素的結果。這裏是用來刪除集合 c 中的元素,因此傳入的變量值爲 false

  3. finally 塊中,將 w 及以後的元素都置爲 null。只留下符合要求的元素。同時,更新 modCount 的次數。

set(int index, E element)

public E set(int index, E element) {
        rangeCheck(index);

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

修改操做很簡單,在數組賦值的基礎上增長了索引值校驗,注意該方法的返回值爲該索引位上的舊值

get(int index)

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

        return elementData(index);
    }
複製代碼

查找操做同修改操做同樣,在數組查找操做上增長索引校驗。

其餘方法

clone()

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);
        }
    }
複製代碼

這裏元素的拷貝調用了 Arrays.copyOf 方法,該方法是淺拷貝,因此 clone 方法呈現的結果也會是淺拷貝,對於拷貝出來的元素進行修改,會同時修改原 ArrayList 中的元素。

總結

ArrayList 的方法都比較簡單,可是其中也有很多細節須要注意。

ArrayList 底層實現是動態數組,因此特色是元素的查找操做快,在末尾插入和刪除元素也很快,時間複雜度都是 O(1)。而在中間插入和刪除元素的均攤時間複雜度爲 O(n) 。

ArrayList 擴容時,其中的 elementData 容量變化會有三種狀況:

1. 當 `ArrayList` 第一次放入 元素時,擴容後新的數組容量爲 1 或者 10;
      2. 當 `ArrayList` 添加元素後容量大於 `MAX_ARRAY_SIZE` 時,容量會變爲 `MAX_ARRAY_SIZE` 或 `Integer.MAX_VALUE`
      3. 其它狀況下,擴容操做使數組容量變爲原來的 1.5 倍
複製代碼

ArrayListclone 方法是淺拷貝。對拷貝出來的實例中的元素進行修改,會改變原來的 ArrayList 實例。

相關文章
相關標籤/搜索