菜鳥學JAVA之——ArrayList源碼解讀(易懂)

ArrayList源碼解讀

ArrayList底層是數組實現的

首先了解幾個必要的成員變量

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
		private static final int DEFAULT_CAPACITY = 10; //默認容量大小爲10
   		private static final Object[] EMPTY_ELEMENTDATA = {};//內容放在這裏,剛開始這個數組對象沒有內容。當用戶指定該 ArrayList 容量爲 0 時,返回該空數組
     
		private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //默認的空的沒有任何內容的數組。當用戶沒有指定 ArrayList 的容量時(即調用無參構造函數),返回的是該數組
    
    /*
    ArrayList基於數組實現,用該數組保存數據, ArrayList 的容量就是該數組的長度
      - 該值爲 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時,當第一次添加元素進入 ArrayList 中時,數組將擴容值 DEFAULT_CAPACITY(10)
      */
  		transient Object[] elementData; 
  		private int size;//記錄容器有效數據個數
}

DEFAULTCAPACITY_EMPTY_ELEMENTDATAEMPTY_ELEMENTDATA 的區別就是:DEFAULTCAPACITY_EMPTY_ELEMENTDATA 數組是默認返回的,而後者是在用戶指定容量爲 0 時返回

1.ArrayList()構造函數

public ArrayList(int initialCapacity) {//可傳參
        if (initialCapacity > 0) {  //初始化容量大小大於0
            this.elementData = new Object[initialCapacity];//new一個新的數組
        } else if (initialCapacity == 0) {  //參數爲0,那就用定義的空數組
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
     public ArrayList() { //不傳參時調用這個構造方法
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    /*
    這裏參數表示:傳一個Collection進來爲了創建ArrayList對象時把Collection裏所有的內容放入ArrayList裏面去。比如傳進一個TreeSet的對象進來
    */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//得到這個對象的數組。方法定義在Collection接口裏
        if ((size = elementData.length) != 0) {//如果當前數組長度不爲空
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //c.toArray可能沒有返回一個Object數組,比如我們下面測試的傳的就是String類型的
            if (elementData.getClass() != Object[].class)//判斷一下
                elementData = Arrays.copyOf(elementData, size, Object[].class); //把elementData裏的內容當作Object複製出來,放入elementData裏
        } else {/
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

2.trimToSize

  public void trimToSize() {
        modCount++;
        if (size < elementData.length) { //如果elementData太長了,比如只放了一個值,size爲1,而數組長度爲10(默認),那就需要把多餘的切掉
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

3.ensureCapacity()
ensureCapacity() 是提供給用戶使用的方法,在 ArrayList 的實現中並沒有使用,他使用的是ensureCapacityInternal

    public void ensureCapacity(int minCapacity) {
        
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) //minExpand:最小擴充容量,默認是10
            ? 0  //如果保存內容的數組 不是 默認的空數組(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)(意思就是用戶指定了長度),那就先不要擴充(哪怕是用戶指定長度爲0也不要擴容,不要問爲什麼,用戶樂意呀)
            : DEFAULT_CAPACITY; //如果是(意思就是通過無參的ArrayList創建的容器,用戶沒有指定長度,默認爲0。如果要添加一個元素則要擴容,擴容的大小就是默認的大小10),那就先擴充10個
			//上面無參構造函數創建後,當元素第一次被加入時,擴容至默認容量 10,就是靠上面這句代碼
        if (minCapacity > minExpand) {  //如果最小容量大於最小擴充,則以用戶指定的爲準,否則還是10
            ensureExplicitCapacity(minCapacity);
        }
    }

4.ensureCapacityInternal()、ensureExplicitCapacity()
add方法在添加元素之前會先調用ensureCapacityInternal方法,主要是有兩個目的:1.如果沒初始化則進行初始化;2.校驗添加元素後是否需要擴容。

 private void ensureCapacityInternal(int minCapacity) {
        // 若 elementData == {},則取 minCapacity 爲 默認容量和參數 minCapacity 之間的最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity= Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

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

        // 如果添加該元素後的大小超過數組大小,則進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//擴容
    }

5.grow() 擴容

private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //新的大小 = 老的大小+老的大小/2    如 oldCapacity = 10,則 newCapacity = 10 + (10 >> 1) = 10 + 5 = 15
        if (newCapacity - minCapacity < 0) //若 newCapacity 依舊小於 minCapacity
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) //若 newCapacity 大於最大存儲容量,則進行大容量分配
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity); //將elementData擴容爲newCapacity大小再賦值回去
    }

把上面的代碼走一遍,方便理解。比如現在新創建一個對象,沒有放任何內容,調用ensureCapacity傳的參數爲13, 這時elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,則minExpand = DEFAULT_CAPACITY=10.然後13>10,調用ensureExplicitCapacity,且把13傳入。因爲minCapacity - elementData.length=13-10 > 0,所以執行grow,newCapacity爲0,newCapacity - minCapacity = 0-13 < 0,所以newCapacity爲13.所以新數組elementData大小變爲13

6.toArray()

 /*
     * 返回 ArrayList 的 Object 數組
     * - 包含 ArrayList 的所有儲存元素
     * - 對返回的該數組進行操作,不會影響該 ArrayList(相當於分配了一個新的數組)==>該操作是安全的
     * - 元素存儲順序與 ArrayList 中的一致
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

7.add()

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //第一件事就是確認長度,因爲如果往裏放一個值則可能導致數組溢出,所以先判斷一下。如果將導致溢出則自動擴容(原來大小的1.5倍)
        elementData[size++] = e;
        return true;
    }

//相當於插入一個元素
public void add(int index, E element) {
        rangeCheckForAdd(index); //檢查index是否合理

        ensureCapacityInternal(size + 1);  //確認長度
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//表示在原來數組的基礎上將index位置上的值拷貝到index+1的位置上,從index開始依次往後拷貝size-index個
    //eg:容量10,有效值有5個,index爲3,則要將3號位置的值拷貝到4號位置,將4號位置的值拷貝到5號位
        elementData[index] = element;//將要加入的元素放在index處
        size++;
    }

8.remove()

/**
     * 移除指定位置的元素
     * index 之後的所有元素依次左移一位
     * @param index 指定位置
     * @return 被移除的元素
     * @throws IndexOutOfBoundsException
     */
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);  //從index+1的位置開始挪,向前挪一位,挪動numMoved個
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

學習參考:
https://blog.csdn.net/qq_33842627/article/details/88701461
https://www.cnblogs.com/gxl1995/p/7534171344218b3784f1beb90d621337.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章