【JAVA集合知識集錦】ArrayList要點總結

ArrayList作爲java中常用的集合類型有哪些特點需要我們瞭解呢?本文將基於jdk1.8源碼來一步步列出ArrayList有哪些需要令人注意的要點。

1 RandomAccess隨機訪問接口

ArrayList繼承實現關係如下圖:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable

從該圖我們可以看出,ArrayList實現了RandmoAccess接口,RandmoAcess接口實現如下:

    public interface RandomAccess {
    }

RandmoAccess接口中實際上並無任何實現,該接口只是表示實現了該接口的類能夠提供快速訪問功能。

2 底層是數組類型

ArrayList實現了RandmoAccess接口代表能夠進行快速訪問,而ArrayList的快速訪問功能實際上是靠數組實現的,下面是ArrayList中最重要的兩個屬性:

    //ArrayList實際存數據的地方
    transient Object[] elementData; 
    //ArrayList的大小
    private int size;

下面我們來看ArrayList的默認構造方法:

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

默認構造方法會將elementData屬性賦爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA,其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA屬性是一個空數組

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

接下來我們看ArrayList中的add和get方法:

    public boolean add(E e) {
        //確認是否擴容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public E get(int index) {
        //檢查是否越界
        rangeCheck(index);

        return elementData(index);
    }

可以看出Arraylist底層是通過數組來操作的。

3 惰性初始化

ArrayList使用默認構造器創建類時創建的是一個空的數組,那麼ArrayList是如何能夠使用add方法存數據的呢?我們可以詳細看看add方法中的ensureCapacityInternal方法源碼:

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //DEFAULT_CAPACITY常量數值是10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        //記錄修改次數
        modCount++;

        if (minCapacity - elementData.length > 0)
            //擴容方法
            grow(minCapacity);
    }
    

從以上源碼可以看出當ArrayList爲一個空數組時,會賦予一個默認的擴容大小10,然後再進擴容方法裏擴容ArrayList,因此ArrayList使用默認構造器初始化時,不會立馬初始化數組大小,而是等待調用add方法後纔會進行初始化,且初始化大小爲10。

4 位運算1.5倍擴容

我們可以查看ArrayList的grow方法來看看arrayList實際是如何擴容的:

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //位運算1.5倍擴容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //判斷minCapacity是否超出最大整數值
            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;
    }

以上代碼分爲如下幾個步驟:

  1. 首先用位運算對原數組進行1.5倍擴容得到新的擴容值newCapacity
  2. 再用新的擴容值newCapacity與實際傳來的所需擴容值minCapacity進行比較,若newCapacity比minCapacity大則用newCapacity,反之則用minCapacity並判斷minCapacity是否合法,這樣得出的值爲一個最終擴容值
  3. 最後用Arrays.copyOf方法對最終選出的擴容值進行擴容
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章