1. 程式人生 > >JDK原始碼閱讀之ArrayList

JDK原始碼閱讀之ArrayList

ArrayList簡介

List 介面的大小可變陣列的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 介面外,此類還提供一些方法來操作內部用來儲存列表的陣列的大小。(此類大致上等同於 Vector 類,除了此類是不同步的。)

從這句話中我們可以看出ArrayList的兩個特點,1-大小可變,2-基於陣列實現

size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時間執行。add 操作以分攤的固定時間 執行,也就是說,新增 n 個元素需要 O(n) 時間。其他所有操作都以線性時間執行(大體上講)。與用於 LinkedList 實現的常數因子相比,此實現的常數因子較低。

從這段話中我們讀出的意思是:add方法與其他方法不一樣,其實他也是這個類的核心方法。

每個 ArrayList 例項都有一個容量。該容量是指用來儲存列表元素的陣列的大小。它總是至少等於列表的大小。隨著向 ArrayList 中不斷新增元素,其容量也自動增長。並未指定增長策略的細節,因為這不只是新增元素會帶來分攤固定時間開銷那樣簡單。

從這裡我們看出:ArrayList中有一個重要的變數:容量,他會貫穿這個類的核心 另外注意一點是:ArrayList不是執行緒安全的,而ArrayList替代的集合類Vector就是一個執行緒安全的類

ArrayList類圖

ArrayList類圖 ArrayList 實現了List介面,RandomAccess介面–代表著可以隨機訪問,實現了Cloneable介面,可以實現複製,這裡的複製不是淺複製是深拷貝。還實現了Serializable介面,實現序列化。繼承了AbstractList介面。

ArrayList的重要方法簡介

構造方法

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

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        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 {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

構造方法一共有三個,其中一個是預設的建構函式,這個函式會初始化一個空陣列,新增元素後會擴容到預設的容量大小10!第二個建構函式是指定容量,還是初始化一個空的陣列,但是新增元素的時候不會擴容到預設的容量大小。第三個建構函式的引數是一個集合,他會將集合中的元素以及大小記錄。(注意:入宮容量為0,或者集合為空,那麼處理方法是和第一個建構函式一致的)

精華方法

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            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;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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);
    }

擴容的步驟可以概括為:

  1. 檢驗容量是否足夠;minCapacity - elementData.length > 0,一旦需要的容量大於現有的容量,那麼立即擴容。
  2. 擴充的容量是當前容量的1.5倍
  3. 檢驗容量是否足夠滿足需要的容量 補充:這裡的擴量的1.5倍沒有使用乘法,而是使用左移運算 oldCapacity + (oldCapacity >> 1)。

重點方法

remove

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);
        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 {
            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, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

從原始碼中我們看出arrayList的移除操作還是比較複雜的,首先是確定需要移除的元素的下標,然後將後面的元素一一的往前移動,將最後一個元素置為null,並且將大小改變。其實從底層的實現我們可以猜到刪除元素這一個過程是怎樣的。 另外他們都有一個共同點就是對modCount這個變數加1了。

clean

public void clear() {
        modCount++;

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

        size = 0;
    }

一個迴圈將所有的元素置為null

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

實現的是深拷貝,不是淺複製

一些簡單的,不常用的就不復制貼上了,閱讀原始碼的時候會有不一樣的感受。

補充

ArrayList也自己實現了兩個迭代器和一個包裝外殼SubList。

ArrayList的閱讀感想

ArrayList和父類相比,最明顯的特點是:父類是不可改變的集合,而ArrayList是大小可變的集合。和Vector類相比,最明顯的特點是:Vector是執行緒安全的,ArrayList不是執行緒安全。所以ArrayList的核心就是怎麼實現大小可變,也就是擴容的方法。

說明

本文是本人撰寫,如果對於讀者有什麼啟發或者幫助,那是我的榮幸。如果對於本文中有什麼錯誤,或者意見,可以留言或者聯絡我:[email protected]