ArrayList 源碼解析

一、ArrayList的概述
ArrayList 是我們開發中常用的一種數據結構,它的底層是基於數組實現的,是一個動態數組,容量你可以動態 增加,ArrayList實現Serializable 接口,他能支持序列化傳輸;實現了RandomAccess接口,支持快速隨機訪問,也就是通過下標可以實現快速訪問;實現了Cloneable 接口,意味着可以被克隆。

二、ArrayList 的源碼解析
首先看下 構造函數

	/**
     * Constructs an empty list with an initial capacity  of ten.
     */
    public ArrayList() {
        this.elementData =  DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

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

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

①、第一個無參構造器 是將一個空數組賦值給 elementData;
②、而第二個有參數構造器是先判斷initialCapacity與0之間的關係,如果大於0,則創建一個大小爲initialCapacity的對象數組賦給elementData;如果等於0,則將EMPTY_ELEMENTDATA賦給 elementData, 如果小於0;則拋出異常;
③、第三個構造器將參數集合轉換成數組判斷集合是否爲空,爲空將 EMPTY_ELEMENTDATA 賦值給elementData,否則將轉換後的數組拷貝到elementData中,由此可知elementData就是ArrayList底層維護的數據

通過上面發現一個神奇的問題:在無參數構造函數中將 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 這個數組賦值給elementData但是在有參構造函數中initialCapacity大小爲0時 將 EMPTY_ELEMENTDATA 賦值給elementData, 帶着疑問繼續看源碼發現:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments  modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData ==  DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY,  minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

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

DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是在calculateCapacity 這個方法裏面使用的,這個方法是判斷默認容量大小的。
如果是無參的構造,他的默認容量會被設置爲10;
而如果是有參的話就會取minCapacity的值,這也體現出jdk源碼設計的微妙,如果我們在有參構造中將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA 那麼就會導致小於10的容量變成10,假如我們只存儲3個元素,那麼無形中就有7個空間被白白的浪費掉了。

接着往下走我們會發現,添加的時候會先去判斷容量是否夠用,如果夠用就不在去擴容,如果不夠用,就進行擴容,擴大到上次的1.5 倍,因爲數組的大小一旦聲明是無法修改的,源碼中是使用了copyOf 函數, copyOf 底層的原理是在內存中重新開闢一塊空間將已有的數據拷貝過去,這樣就在不影響原有數據的基礎上進行了擴容,可以編寫一個demo驗證一下:

public static void main(String[] args) {
          
//        List<String> list = new ArrayList<>();
//        System.out.println(list);
          
          Object[] object = {1,2,3};
          System.out.println(object);
          object = Arrays.copyOf(object, 10);
          System.out.println(object);
     }

運行後發現使用 copyof 函數後並沒有指向當前數組,而是指向了其他,這也就證明了他不會在當前數組進行擴容,而是在其他的地方重新創建了一塊空間。
假設數據量很大的時候,會導致效率大大的降低,所以以後在使用ArrayList 的時候即使不知道具體大小,也可以指定一個大概的容量,這也就可以避免重複的擴容,降低效率.

通過剛纔的分析,還發現了一個問題是add 方法居然沒有加鎖,這樣如果在多線程的情況下,ArrayList 的add方法是線程不安全的,容易產生數組越界問題.
1.當線程A調用ensureCapacityInternal 方法時 這時size=9 判斷容量後發現不用擴容
2.當線程B 進入時 他得到size 也是9,而elementData的大小是10,判斷容量後發現不用擴容
3.當線程A去執行elementData[size++] = e 操作時,size++, size 的大小變成了10,
4.當線程B再去執行elementData[size++] = e 操作時,就會產生數組越界的問題,
解決方法:爲了解決線程安全問題可以使用 Collections.synchronizedList(),但是會降低效率 如:
List list = Collections.synchroizedList(new ArrayList<>());

刪除 remove():

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);
        elementData[--size] = null; // clear to let GC do  its work
        return oldValue;
    }

刪除的方法比較簡單,每次進行數組的複製,然後將舊的 elementData=null 讓垃圾回收機制去回收。
Arrylist 的 get ,set,clear 也沒有其他複雜的操作, 都是一些對數組的簡單操作。

ArrayList的優點
1.ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess接口,因此查找也就是get的時候非常快。
2.可以做到自動擴容,無需開發者關心數組大小

ArrayList的缺點
1.因爲每次插入和刪除都需要對數組進行拷貝操作,所以插入和刪除的效率並不高
2.是線程不安全

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