ArrayList擴容原理分析


1:代碼解讀和分析

1.1:構造方法分析

1:

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時,立即new Object[initialCapacity];在一開始的時候就有了指定大小的數組在ArrayList內部;當initialCapacity==0時,則將EMPTY_ELEMENTDATA給elementdata,其中EMPTY_ELEMENTDATA是一個空數組。

這種情況下,size會是幾?

2:

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

第二個構造方法是無參構造方法。在這里直接將this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA一樣如下

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] 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 = {};

都是用private static final Object[]定義的兩個空數組。

3:

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

第三種構造方法是用通過放入實現Collection接口的容器來當作該構造方法的初始數據,通過toArray方法將內部的數據拿出來,給

elementData的方法來構建一個ArrayList出來。如果形參集合的數據長度為0,還是要將EMPTIYDATA數組賦給elementData的;

若源集合內的數組長度不為0后,在判斷數組類型,如果不為Object基本類型的化,則重新通過Arrays.copyOf方法創建一個新的數組,數據,長度不變,類型設置成Object類型。

注意:這里的size也已經在構造方法中更新了。

1.2:add方法分析

默認在數組尾部增加一個對象。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

在add放法內部,使用ensureCapacityInternal(size + 1);方法,下面我們看一下該方法的作用。

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

方法名字為:保證內部容量,該方法內部又通過calculateCapacity方法來計算最小的容量,輸入的minCapacity是size+1,即在增加數據之前來引入當前數據所用的最小的數據長度+1為計算標准進行判斷。

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

上面是calculateCapacity方法的內部的功能,分析如下如果當前ArrayList是通過無參的構造方法創建,且當前是第一次add數據,則返回默認的的DEFAULT_CAPACITY和minCapacity的較大值。其中前者是10,則在第一次add數據,當size+1=1小於DEFAULT_CAPACIT時,都回返回10。顯然當不是第一次add數據時,在這里的if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ,顯然不會成立了,則返回minCapacity!

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

經過calculateCapacity方法返回的結果送到ensureExplicitCapacity(int minCapacity)方法中,當第一次add數據時,經過calculateCapacity返回的結果是10,即默認的值,則會執行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);
    }

在grow方法中,將原始數組長度保存在oldCapacity中,將newCapacity取值為oldCapacity的1.5倍。那如果newCapacity小於輸入的minCapacity,那么就會將minCapacity的值給newCapacity,則根據newCapacity的大小和原始的elementData通過Arrays.copyOf方法創建一個新的數據即可!

總結:通過無參ArrayList構造函數創建的ArrayList的內部數組是指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在第一次add數據時,會通過上面的步驟,創建一個大小為10的elementData的數據在內部!通過指定大小的

1.2.1:非首次add

經過上面分析,我們再來分析一下第二次add數據的情況。此時ensureCapacityInternal(size + 1); size = 1,則ensureCapacityInternal(2),則calculateCapacity(Object[] elementData, int minCapacity),中minCapacity為2,直接返回2。再進入

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
其中,minCapacity為2,顯然小於elementData.length,所以不會擴容!

1.2.2:數組已滿時add

經過上面分析,我們再來分析一下在內置的數據滿的時候的add數據的情況,按照1.2.1中分析的那樣,再

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
中如果minCapacity大於elements.length時會進行grow,按照原來數組長度的1.5倍進行,擴容。

 

2:總結

1:ArrayList在使用無參構造方法創建時,在創建后到add前,其內部是一個空數組,當add第一個數據后,內部數組變成長度為10,當內部數組已滿時,就變成原來數組長度的1.5倍!

2:使用指定長度,會立即創建一個指定大小的數據在內部。

3:指定集合參數的構造方法是,會將根據源集合數據創建一個長度,數據與源集合相等的Object類型的內部數組。

 

3:對比LinkedList

LinkedList底層是用雙向鏈表實現的,所以它對元素的增加、刪除效率要比ArrayList好;它是一個雙向鏈表,沒有初始化大小,也沒有擴容的機制,就是一直在前面或者后面新增就好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM