天天看點

ArrayList擴容源碼解析(JDK1.8)

環境:JDK1.8

ArrayList擴容源碼解析(JDK1.8)

先看看幾個ArrayList内部成員變量:

ArrayList擴容源碼解析(JDK1.8)

ArrayList的容量大小

ArrayList擴容源碼解析(JDK1.8)

預設的初始容量為10:DEFAULT_CAPACITY = 10。

ArrayList擴容源碼解析(JDK1.8)

對于所有的空執行個體可共享的空陣列執行個體EMPTY_ELEMENTDATA。

ArrayList擴容源碼解析(JDK1.8)

對于所有的空執行個體可共享的空陣列執行個體DEFAULTCAPACITY_EMPTY_ELEMENTDATA,不同于EMPTY_ELEMENTDATA在于該數組可記錄第一個元素被添加的時候,容器至少需要多大的空間

ArrayList擴容源碼解析(JDK1.8)

存儲ArrayList元素的數組緩沖區,ArrayList的容量即為該陣列資料緩沖區的長度。當添加第一個元素時,任意剛開始容量為DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList的容量都會擴充到DEFAULT_CAPACITY,也就是10。

ArrayList擴容源碼解析(JDK1.8)

内部數組可以配置設定的最大容量MAX_ARRAY_SIZE=Integer.MAX_VALUE(為整型數最大值)-8,為何要減8呢?因為這個8的空間用于存儲數組_length字段。

檢視add(E e)方法:

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
           

檢視ensureCapacityInternal(int minCapacity)方法、ensureExplicitCapacity(int minCapacity)方法以及calculateCapacity(Object[] elementData, int minCapacity)方法:

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

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

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

ensureExplicitCapacity方法實作:

1. modCount++。

2. 如果需擴充的容量大小minCapacity大于原内部數組大小,則調用grow方法。

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

calculateCapacity方法實作:

1. 傳入原内部數組elementData和需擴充的容量大小minCapacity。

2. 如果elementData為空數組則比較預設初始容量DEFAULT_CAPACITY=10與參數minCapacity的大小,取大傳回。

3. 如果elementData不為空,則傳回minCapacity。

檢視grow(int minCapacity)方法:

增加容量以確定ArrayList能保留指定的最小容量參數下的元素數量

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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方法實作:

a. 取得elementData的長度為原容量oldCapacity。

b. oldCapacity >> 1表示位運算,為oldCapacity轉換成二進制後向右移動1位,目前可了解為java中oldCapacity / 2的結果,以後會專門寫一篇文章來說明位運算。故可以了解為新容量newCapacity為oldCapacity+1/2的oldCapacity,此時再比較newCapacity和參數要求容量(至少得需要到參數要求的容量大小,不然無法将元素存放到ArrayList),如果小于則newCapacity設定為minCapacity,否則再比較newCapacity和MAX_ARRAY_SIZE,如果前者大于後者,則檢視hugeCapacity(int minCapacity)方法

ArrayList擴容源碼解析(JDK1.8)

當容量參數小于0,抛出異常OutOfMemoryError()。如果參數大于MAX_ARRAY_SIZE,則傳回Integer.MAX_VALUE(為整型數最大值),否則傳回MAX_ARRAY_SIZE。最後将傳回值賦給newCapacity

c. 将原内部數組和新的容量長度融合為新的内部數組對象(内容不變,容量擴充)。

結論:

1. ArrayList對象群初始化時可不傳參,共享内部數組對象,大小為空。

2. 當添加第一個元素時,内部擴容為預設大小10。

3. 當添加到大于10個元素時,則以後每次需擴容時,規則為newCapacity(新容量) = oldCapacity(原容量) +oldCapacity/2。

4. 将最新添加的元素放入目前ArrayList内部數組的size++位,即elementData[size++] = e;

額外說明:ArrayList從字面上看,底層是用數組實作的,增删查等操作也和數組的這類操作特性一緻。

打個比方:

1. 添加第1個元素則内部數組擴充為預設大小10即elementData的大小為10。

2. 添加第11個元素時,則一直執行到grow方法,傳參為11,oldCapacity為10,newCapacity變為15,elementData的大小為15,size當下為15,elementData[size++] = e,設定第11個數組元素。

3. 添加第16個元素時,則一直執行到grow方法,傳參為16,oldCapacity為15,newCapacity變為22,elementData的大小為22,size當下為15,elementData[size++] = e,設定第16個數組元素。

以此類推...

解決具體面試題:

public static ArrayList<Integer> getIntegerList(int capacity) {
		ArrayList<Integer> list = new ArrayList<Integer>();
		if (capacity <= 0) {
			return list;
		}
		for (int i = 0; i < capacity; i++) {
			list.add(i);
		}
		return list;
	}
	public static void main(String[] args) {
		getIntegerList(64);
	}
           

問題:以上程式中的list擴容了幾次?

結合上述内容,則可以繼續推倒:

4. 當添加第23個元素時,則newCapacity變為33,elementData的大小為33,設定第23個數組元素。

5. 當添加第34個元素時,則newCapacity變為51,elementData的大小為51,設定第34個數組元素。

6. 當添加第52個元素時,則newCapacity變為78,elementData的大小為78,設定第52個數組元素。

64在第78個元素之前,則經過6次擴容即可滿足list.add(63)的需求。

答案為:6次