ArrayList源碼詳解(JDK1.8版)

一. 概述

        ArrayList是一個非線程安全的有序集合, 按照添加順序排列, 能夠隨機添加或刪除元素, 支持任意類型元素, 支持泛型。java

二. JDK版本差別

      對於ArrayList,  JDK1.8相對於JDK1.9沒有什麼大變化,  JDK1.8版本比JDK1.7版增長了一些函數編程特性方法, 以下編程

//循環每一個元素, 以元素爲參數執行action操做
    @Override
    public void forEach(Consumer<? super E> action) {
        ...
    }
     
    //經過Predicate(斷言函數接口)實現過濾刪除
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        ....

        return anyToRemove;
    }
    

    //經過UnaryOperator(一元操做函數接口)來實現替換    
    @Override
    @SuppressWarnings("unchecked")
    public void replaceAll(UnaryOperator<E> operator) {
        ...
    }

      

三. 數據結構     

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

      從以上源碼能夠看出:數組

  • ArrayList使用一個Object數組存儲元素, 說明是有序的
  • elementData 聲明爲transient,  不參與序列化
  • 非私有實例變量, 容許內部類訪問, ArrayList存在一些內部類須要操做elementData,                       如: ListItr,SubList,ArrayListSpliterator。

   

四. 實例初始化

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

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

   
    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實例化有如下三種:安全

  • public ArrayList(int initialCapacity): 指定容量的初始化. 若是initialCapacity>0, 則建立一個大小爲initialCapacity的數組,若是initialCapacity=0, 則建立一個空數組(EMPTY_ELEMENTDATA), 若是小於0, 則拋出異常.
  • public ArrayList(): 建立一個空數組
  • public ArrayList(Collection<? extends E> c): 經過外部集合初始化.將集合c轉爲數組, 賦給本身數組elementData, 若是數組類型不是 Object[], 則經過Arrays.copyOf()轉換。

五. 擴容機制

private void grow(int minCapacity) {
        //舊容量
        int oldCapacity = elementData.length;
        //計算新容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //判斷是否超過數組容量上限
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 複製一個新容量的數組
        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;
    }

擴容經過grow()方法實現, 主要作了如下三項工做:數據結構

  •  計算ArrayList新的容量.                                                                                                                  int newCapacity = oldCapacity + (oldCapacity >> 1)
      oldCapacity >> 1 :  移位運行, 至關於除2,  表明oldCapacity(原容量)的1/2
      也就是說ArrayList擴容大小爲原容量的二分之一.
  • 若是newCapacity(擴容後的容量)大於MAX_ARRAY_SIZE(容量上限), 則設置爲Int類型的最大值
  • 使用Arrays.copyOf()方法複製一個新容量的數組

六. 元素添加

/**
     * 添加一個元素, 支持任何類型
     *
     */
    public boolean add(E e) {
        //確保內部容量能夠支持添加的元素
        ensureCapacityInternal(size + 1);
        //向數組中添加元素
        elementData[size++] = e;
        return true;
    }

    /**
     * 將元素添加到特定的索引位置
     *
     * @param index 待添加元素的索引
     * @param element 待添加元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //檢查index是否超出數組的index限制,若是超過, 
        //則拋出數組越界異常(IndexOutOfBoundsException)
        rangeCheckForAdd(index);

        //確保內部容量能夠支持添加的元素
        ensureCapacityInternal(size + 1); 
        //複製一個新數組, 目的是爲了移位,將插入點index以後的元素日後移動
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
 
    //確保內部容量能夠支持添加的元素
    private void ensureCapacityInternal(int minCapacity) {
        //若是數組爲空, 多是單元素添加或批量元素添加觸發當前操做
        //單元素添加: minCapacity = DEFAULT_CAPACITY(至關於初始化默認容量)
        //批量元素添加: minCapacity不變
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //確保準確的容量
        ensureExplicitCapacity(minCapacity);
    }

    //確保準確的容量
    private void ensureExplicitCapacity(int minCapacity) {
        //解決併發問題的,後續會提到
        modCount++;

        // elementData新增元素後, 若是當前容量不能知足elementData的長度, 則進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
  •  ArrayList添加元素時, 會先檢查容量, 容量不夠,則擴容,  擴容會阻塞元素添加.
  • 按index插入元素會觸發元素移位, 這樣會影響性能, 插入位置越靠前, 影響越大
  • ArrayList經過System.arraycopy()方法實現的移位, 這個方法能夠將A數組的某個索引後的元素複製到B數組裏, 組成一個新的數組

七. 元素刪除

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

   
    public boolean remove(Object o) {
        if (o == null) {
            //查找爲null的元素, 並刪除
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            //查找不爲null的元素,並刪除
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 快速刪除, 不校驗index溢出, 不返還刪除元素對象
     */
    private void fastRemove(int index) {
        modCount++;
        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
    }
  •     ArrayList刪除元素時, 一樣須要移動元素,不過是向前移動, 數據量大時, 影響性能, 刪除位置index越靠前, 影響越大

八. 元素獲取

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
  •  元素獲取比較簡單,  先校驗index是否數組越界, 而後經過index直接從elementData數組中獲取元素,說明ArrayList的獲取操做效率很高

九. 線程安全

     ArrayList的 add, remove,get操做都沒有加鎖, 因此是非線程安全的。併發

十. modCount是幹什麼用的?

protected transient int modCount = 0;

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

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

 這個變量充斥着整個ArrayList類中, 凡有修改的地方就有他它,   它記錄着ArrayList元素修改次數。ide

下面來講明它的用處:函數

//迭代器
     public ListIterator<E> listIterator(final int index) {
            //檢查修改狀態(modCount)
            checkForComodification();
            rangeCheckForAdd(index);
            final int offset = this.offset;

            return new ListIterator<E>() {

                @SuppressWarnings("unchecked")
                public E next() {
                    checkForComodification();
                    ......
                }

                public void remove() {
                    if (lastRet < 0)
                        throw new IllegalStateException();
                    checkForComodification();

                    try {
                        ......
                    } catch (IndexOutOfBoundsException ex) {
                        throw new ConcurrentModificationException();
                    }
                }

                public void add(E e) {
                    checkForComodification();

                    try {
                        ......
                    } catch (IndexOutOfBoundsException ex) {
                        throw new ConcurrentModificationException();
                    }
                }

                final void checkForComodification() {
                    //若是不相等, 說明其餘線程修改了ArrayList元素
                    if (expectedModCount != ArrayList.this.modCount)
                        throw new ConcurrentModificationException();
                }
                
               ......
            };
        }

        能夠看出在ListIterator迭代器中, 進行任何操做都會調用checkForComodification() 檢查modCount是否有變更, 若是有變更, 就表明其餘線程修改了ArrayList的元素,而後拋出ConcurrentModificationException異常.性能

     它的做用是用於實現快速失敗機制(fail-fast), 當咱們對ArrayList的元素迭代時, 若是其它線程修改了某個元素, 會產生迭代結果會不正確或迭代失敗, 快速失敗機制能夠提早檢測迭代失敗問題, 避免浪費時間, 影響性能。ui

十一. 總結

  •  ArrayList是一個非線程安全的有序集合.
  •  ArrayList在插入(非末尾插入)元素時, 須要向後移動內部元素, 添加位置越靠前, 性能影響越大.
  •  ArrayList在刪除(非末尾刪除)元素時, 須要向前移動內部元素, 刪除位置越靠前, 性能影響越大.
  •  ArrayList採用動態擴容機制, 每次擴容原容量的一半, 也就是1.5倍. 最大爲Integer的最大值.
  •  ArrayList 經過Arrays.copyOf()方法實現的擴容
  •  ArrayList 經過System.arraycopy()方法實現元素的移位
  •  ArrayList 經過modCount實現了fail-fast機制
相關文章
相關標籤/搜索