1. 程式人生 > >ArrayList原始碼分析---擴容機制

ArrayList原始碼分析---擴容機制

一  ArrayList建構函式

我們從原始碼中可以看到, ArrayList共有三個建構函式(包含一個無參建構函式和兩個有參建構函式), 所以預設初始化ArrayList的時候它的值是為{}, 即它的容量是為0的

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

當我們指定容量時, 會呼叫方法, 容量為0時依然返回{}, 容量大於0返回Object[指定容量], 否則拋IllegalArgumentException異常

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

當我們傳入一個集合時, 預設初始化成一個Object[], 陣列的length屬性不為0時, 返回一個呼叫Arrays.copyOf()生成的Object陣列, 否則返回{}

/**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    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;
        }
    }

以無引數構造方法建立 ArrayList 時,實際上初始化賦值的是一個空陣列。當真正對陣列進行新增元素操作時,才真正分配容量。即向陣列中新增第一個元素時,陣列容量擴為10。下面詳細解析

二  ArrayList擴容機制

1,  先看看add()方法

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

每次會在list的末尾新增新元素, 呼叫 對ArrayList的容量進行處理, 然後對陣列下一個下標進行賦值, 最後我們發現ArrayList新增元素的實質就是對陣列進行賦值!

2,  再看看ensureCapacityInternal() 方法

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

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

 

會計算出一個最小擴容量

3,  看看ensureExplicitCapacity() 方法

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
  • 當我們要 add 進第1個元素到 ArrayList 時,elementData.length 為0 (因為還是一個空的 list),因為執行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此時為10。此時,minCapacity - elementData.length > 0 成立,所以會進入 grow(minCapacity) 方法。
  • 當add第2個元素時,minCapacity 為2,此時e lementData.length(容量)在新增第一個元素後擴容成 10 了。此時,minCapacity - elementData.length > 0 不成立,所以不會進入 (執行)grow(minCapacity) 方法。
  • 新增第3、4···到第10個元素時,依然不會執行grow方法,陣列容量都為10。

直到新增第11個元素,minCapacity(為11)比elementData.length(為10)要大。進入grow方法進行擴容。

4, grow()

 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
// oldCapacity為舊容量,newCapacity為新容量
        int oldCapacity = elementData.length;
        //將oldCapacity 右移一位,其效果相當於oldCapacity /2,
        //我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麼就把最小需要容量當作陣列的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
       // 如果新容量大於 MAX_ARRAY_SIZE,進入(執行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE,
       //如果minCapacity大於最大容量,則新容量則為`Integer.MAX_VALUE`,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 `Integer.MAX_VALUE - 8`。
        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);

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次擴容之後容量都會變為原來的 1.5 倍!

並不是部分人說的2倍或者增加5的容量

grow() 方法 :

  • 當add第1個元素時,oldCapacity 為0,經比較後第一個if判斷成立,newCapacity = minCapacity(為10)。但是第二個if判斷不會成立,即newCapacity 不比 MAX_ARRAY_SIZE大,則不會進入 hugeCapacity 方法。陣列容量為10,add方法中 return true,size增為1。
  • 當add第11個元素進入grow方法時,newCapacity為15,比minCapacity(為11)大,第一個if判斷不成立。新容量沒有大於陣列最大size,不會進入hugeCapacity方法。陣列容量擴為15,add方法中return true,size增為11。
  • 以此類推······

這裡補充一點比較重要,但是容易被忽視掉的知識點:

  • java 中的 length 屬性是針對陣列說的,比如說你聲明瞭一個數組,想知道這個陣列的長度則用到了 length 這個屬性.
  • java 中的 length() 方法是針對字串說的,如果想看這個字串的長度則用到 length() 這個方法.
  • java 中的 size() 方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就呼叫此方法來檢視!

5,  hugeCapacity() 方法

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

 如果新容量大於 MAX_ARRAY_SIZE,執行 hugeCapacity() 方法來比較 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大於最大容量,則新容量則為Integer.MAX_VALUE,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 Integer.MAX_VALUE - 8

疑問1: 傳入集合的建構函式裡這句話比較的意義是什麼? 原始碼181行(JDK1.8)

if (elementData.getClass() != Object[].class)

疑問2: hugeCapacity() 中可以發現arrayList 最大容量是 Integer.MAX_VALUE, 那MAX_ARRAY_SIZE(值為Integer.MAX_VALUE-8)的意義是什麼? 原始碼268行

網上說是防止記憶體溢位, 或者說是儲存 Headerwords, 但仍是不解為什麼是減少8, 希望大佬能給予講解