jdk8下面的ArrayList的擴容


一、 ArrayList

class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • ​ ArrayList是基於數組實現的,是一個動態數組,其容量能夠自動增長

  • ​ ArrayList是線程不安全的

  • ​ 實現了RandomAccess接口,所以支持快速訪問,

  • ​ 實現了Cloneable 接口, 能夠被克隆

  • ​ 實現了Serializable接口,支持序列化

二、 ArrayList的構造函數

 	private static final long serialVersionUID = 8683452581122892189L;
 	/**
     * 默認初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 用於空實例的共享空數組實例
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     *用於默認大小的空實例的共享空數組實例。我們將其與EMPTY_ELEMENTDATA區分開來,以了解何時 		充氣多少添加第一個元素。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    / * *
      * 存儲ArrayList元素的數組緩沖區。
      * ArrayList的容量是這個數組緩沖區的長度。任何
      *使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList
      *將在添加第一個元素時擴展為DEFAULT_CAPACITY,也就是10。
       * /
    transient Object[] elementData;
    
    private int size;
    
    /**
     *默認構造函數,使用初始容量10構造一個空列表(無參數構造)
     */
    public ArrayList() {
        //上面的Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    /**
     * 帶初始容量參數的構造函數。(用戶自己指定容量)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//初始容量大於0
            //創建initialCapacity大小的數組
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//初始容量等於0
            //創建空數組  上面的 Object[] EMPTY_ELEMENTDATA = {};
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//初始容量小於0,拋出異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


   /**
    *構造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回
    *如果指定的集合為null,throws NullPointerException。 
    */
     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;
        }
    }

​ 仔細觀察的話可以發現在以無參構造方法或者有參構造但是參數默認為0時創建的ArrayList,實際上初始化賦值的是一個空數組。

三、 進一步分析ArrayList的add()

​ 方便起見,這里以無參構造函數創建的ArrayList為例來分析

​ 1、先看下Add()方法

    /**
     * 將指定的元素追加到此列表的末尾.
     *
     * @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;
    }

​ 2、 這里面用到了ensureCapacityInternal()方法

​ add方法里面調用了 ensureCapacityInternal(size + 1),當添加的是第一個元素的時候(size + 1 == 1);Math.max(DEFAULT_CAPACITY, minCapacity)里面獲取到的最大值是10,

   // 獲取要擴大的容量
   private void ensureCapacityInternal(int minCapacity) {
   		//上面默認的是 Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// DEFAULT_CAPACITY = 10, 獲取兩者中的最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

​ 3、 接着又調用了ensureExplicitCapacity()方法

 // 判斷是否需要擴容
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code   有溢出的代碼
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

先來分析一波:

​ 當我們添加第一個元素的時候,elementData.length是等於0的(因為默認它是一個空的list),之后會執行ensureCapacityInternal()方法,此時minCapacity大小是10,minCapacity - elementData.length > 0 這個條件成立。接着就會調用grow(minCapacity) 方法。

​ 但是當我們添加第二個元素的時候,elementData.length在添加第一個元素之后就變成了10,minCapacity - elementData.length > 0 判斷不通過,所以不會執行grow(minCapacity) 方法

​ 當添加到第11個元素的時候 11 >10 所以會繼續調用grow(minCapacity) 方法。

4、里面又用到了grow(minCapacity) 方法

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    /**
     * 增加容量,確保他至少能夠容納最小容量參數指定的元素,
     * 初次添加元素的時候minCapacity 是等於10 的
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // >> 右移,右移一位,相當於除以2,oldCapacity >> 1 = oldCapacity/2
        // 將原有容量擴大為原來的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //比較新容量和最小容量,如果新容量小於最小容量,將最小容量賦值為新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
            
         // 如果新容量大於MAX_ARRAY_SIZE,就執行hugeCapacity 方法
        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);
    }

​ 當添加第一個元素的時候,oldCapacity為0 ,經過判斷第一個條件成立,所以newCapacity 現在等於10。但是第二個if條件判斷不成立,所以不會進入hugeCapacity 方法。

​ 當添加第11個元素的時候,oldCapacity為10,newCapacity為15 大於minCapacity,所以第一個條件判斷不成立。第二個if條件判斷也不成立,所以不會進入到hugeCapacity 方法。

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

四、 ArrayList.add(int index, E element)

​ 在指定位置添加元素需要先對index進行邊界檢查,查看它是否越界,之后調用ensureCapacityInternal來確保capacity足夠大。再將從index開始之后的所有元素后移一位,將element插入到index位置,最后size加1.

    /**
     *  將指定元素插入到list中的指定位置
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
         //arraycopy()方法實現數組自己復制自己
        //elementData:源數組;index:源數組中的起始位置;elementData:目標數組;index +1:目標數組中的起始位置; size - index:要復制的數組元素的數量;
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

里面調用了rangeCheckForAdd方法

   // 判斷當前索引是否越界
   private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }


免責聲明!

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



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