ArrayList源碼分析

總覽

clipboard.png

  • 底層:ArrayList底層是一個數組,能夠擴容,正由於它擴容,因此它可以實現「動態」增加
  • 容許null元素
  • 時間複雜度:size、isEmpty、get、set、iterator和listIterator方法都以固定時間運行,時間複雜度爲O(1)。add和remove方法須要O(n)時間。與用於LinkedList實現的常數因子相比,此實現的常數因子較低。
  • 容量:ArrayList的容量能夠自動增加。
  • 是否同步:ArrayList不一樣步的。
  • 迭代器:ArrayList的iterator和listIterator方法返回的迭代器是fail-fast的。

定義

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io. Serializable
  • ArrayList<E>:說明ArrayList支持泛型。
  • extends AbstractList<E>:繼承了AbstractList。AbstractList提供List接口的骨幹實現,以最大限度地減小「隨機訪問」數據存儲(如ArrayList)實現Llist所需的工做。
  • implementsjava

    • List<E>:實現了List。實現了全部可選列表操做。
    • RandomAccess:RandomAccess 是一個標誌接口,代表實現這個這個接口的 List 集合是支持快速隨機訪問的。在 ArrayList 中,咱們便可以經過元素的序號快速獲取元素對象,這就是快速隨機訪問。
    • Cloneable:代表其能夠調用clone()方法來返回實例的field-for-field拷貝。
    • java.io.Serializable:代表該類具備序列化功能。

關鍵屬性

/**
     * 默認初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 指定該ArrayList容量爲0時,返回該空數組。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 當調用無參構造方法,返回的是該數組。剛建立一個ArrayList 時,其內數據量爲0。
     * 它與EMPTY_ELEMENTDATA的區別就是:該數組是默認返回的,然後者是在用戶指定容量爲0時返回。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存ArrayList數據的數組
     * 該值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時,當第一次添加元素進入ArrayList中時,數組將擴容值DEFAULT_CAPACITY。
     */
    transient Object[] elementData; 

    /**
     * ArrayList 所包含的元素個數
     */
    private int size;

問:elementData被標記爲transient,那麼它的序列化和反序列化是如何實現的呢?
答:ArrayList自定義了它的序列化和反序列化方式。詳情請查看writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectOutputStream s)方法。git

構造方法

ArrayList提供了三種構造方法。github

  • ArrayList(int initialCapacity):構造一個指定容量爲capacity的空ArrayList。
  • ArrayList():構造一個初始容量爲 10 的空列表。
  • ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。
/**
     * 帶初始容量參數的構造函數。(用戶本身指定容量)
     */
    public ArrayList(int initialCapacity) {
        //初始容量大於0
        if (initialCapacity > 0) {
            //建立initialCapacity大小的數組
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //建立空數組
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * 默認構造函數,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 爲0.初始化爲10,
     * 也就是說初始實際上是空數組 當添加第一個元素的時候數組容量才變成10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。
     * 若是指定的集合爲null,throws NullPointerException。
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        //若是指定集合元素個數不爲0
        if ((size = elementData.length) != 0) {
            // c.toArray 可能返回的不是Object類型的數組因此加上下面的語句用於判斷,
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            //【2】Arrays包含用來操做數組(好比排序和搜索)的各類方法。
            //copyOf(U[] original, int newLength, Class<? extends T[]> newType)
            // 複製指定的數組,截取或用 null 填充(若有必要),以使副本具備指定的長度。
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

核心方法

clipboard.png

get(int index)

步驟:segmentfault

  • 檢查角標
  • 返回元素
/**
     * 返回此列表中指定位置的元素。
     * 
     * @param  index 須要返回的元素的索引
     * @return list中索引爲index的元素
     * @throws IndexOutOfBoundsException 若是索引超出size
     */
    public E get(int index) {
        //越界檢查
        rangeCheck(index);

        return elementData(index);
    }
    
    /**
     * 檢查給定的索引是否在範圍內。
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    /**
     * 返回IndexOutOfBoundsException細節信息
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }
    
    /**
     * 返回索引爲index的元素
     */
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

add(E e)

步驟:數組

  • 檢查是否須要擴容
  • 插入元素

整個擴容過程:安全

  • 首先去檢查一下數組的容量是否足夠多線程

    • 足夠:直接添加
    • 不足夠:擴容dom

      • 擴容到原來的1.5倍
      • 第一次擴容後,若是容量仍是小於minCapacity,就將容量擴充爲minCapacity。
/**
     * 將指定的元素追加到此列表的末尾。
     */
    public boolean add(E e) {
        //確認list容量,若是不夠,容量加1。注意:只加1,保證資源不被浪費
        ensureCapacityInternal(size + 1);
        //這裏看到ArrayList添加元素的實質就至關於爲數組賦值
        elementData[size++] = e;
        return true;
    }

擴容ide

/**
     * ArrayList的擴容機制
     * ArrayList的擴容機制提升了性能,若是每次只擴充一個,
     * 那麼頻繁的插入會致使頻繁的拷貝,下降性能,而ArrayList的擴容機制避免了這種狀況。
     * 若有必要,增長此ArrayList實例的容量,以確保它至少能容納元素的數量
     * @param   minCapacity   所需的最小容量
     */
    public void ensureCapacity(int minCapacity) {
        // 若是elementData等於DEFAULTCAPACITY_EMPTY_ELEMENTDATA,最小擴容量爲DEFAULT_CAPACITY,不然爲0
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    /**
     * 數組容量檢查,不夠時則進行擴容。
     *
     * @param minCapacity    想要的最小容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 若elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
            // 則取minCapacity爲DEFAULT_CAPACITY和參數minCapacity之間的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    //獲得最小擴容量
    private void ensureCapacityInternal(int minCapacity) {
        // 獲取默認的容量和傳入參數的較大值
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

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

        // 確保指定的最小容量 > 數組緩衝區當前的長度
        if (minCapacity - elementData.length > 0)
            //擴容
            grow(minCapacity);
    }

    /**
     * 要分配的最大數組大小
     * 爲何要減去8呢?
     * 由於某些VM會在數組中保留一些頭字,嘗試分配這個最大存儲容量,可能會致使array容量大於VM的limit,最終致使OutOfMemoryError。
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList擴容的核心方法
     *
     * 第一次擴容,邏輯爲newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基礎上增長一半。
     * 第一次擴容後,若是容量仍是小於minCapacity,就將容量擴充爲minCapacity。
     */
    private void grow(int minCapacity) {
        // oldCapacity爲舊容量,newCapacity爲新容量
        int oldCapacity = elementData.length;
        //將oldCapacity 右移一位,其效果至關於oldCapacity /2,
        //咱們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新爲舊容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //而後檢查新容量是否大於最小須要容量,若仍是小於最小須要容量,那麼就把最小須要容量看成數組的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //再檢查新容量是否超出了ArrayList所定義的最大容量,
        //若超出了,則調用hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE,
        //若是minCapacity大於MAX_ARRAY_SIZE,則新容量則爲Interger.MAX_VALUE,不然,新容量大小則爲 MAX_ARRAY_SIZE。
        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);
    }

    //比較minCapacity和 MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

看完了代碼,能夠對擴容方法總結以下:函數

  • 進行空間檢查,決定是否進行擴容,以及肯定最少須要的容量
  • 若是肯定擴容,就執行grow(int minCapacity),minCapacity爲最少須要的容量
  • 第一次擴容,邏輯爲newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基礎上增長一半。
  • 第一次擴容後,若是容量仍是小於minCapacity,就將容量擴充爲minCapacity。
  • 對擴容後的容量進行判斷,若是大於容許的最大容量MAX_ARRAY_SIZE,則將容量再次調整爲MAX_ARRAY_SIZE。至此擴容操做結束。

add(int index, E element)

步驟:

  • 越界檢查
  • 空間檢查,若是有須要進行擴容
  • 插入元素
/**
     * 在此列表中的指定位置插入指定的元素。
     * 先調用 rangeCheckForAdd 對index進行界限檢查;而後調用 ensureCapacityInternal 方法保證capacity足夠大;
     * 再將從index開始以後的全部成員後移一個位置;將element插入index位置;最後size加1。
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1); 
        //arraycopy()這個實現數組之間複製的方法必定要看一下,下面就用到了arraycopy()方法實現數組本身複製本身
        //實現讓index開始以後的全部元素後移一個位置:
        //elementData:源數組;index:源數組中的起始位置;elementData:目標數組;index + 1:目標數組中的起始位置; size - index:要複製的數組元素的數量;
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

add(int index, E e)須要先對元素進行移動,而後完成插入操做,也就意味着該方法有着線性的時間複雜度,即O(n)

remove(int index)

步驟:

  • 檢查角標
  • 刪除元素
  • 計算出須要移動的個數,將索引大於index的元素左移一位(左移後,該刪除的元素就被覆蓋了,至關於被刪除了)
  • 設置爲null(size-1處),讓Gc回收
/**
     * 刪除該列表中指定位置的元素。 將任何後續元素移動到左側(從其索引中減去一個元素)。
     */
    public E remove(int index) {
        //檢查索引是否越界
        rangeCheck(index);
        //結構性修改次數+1【1】
        modCount++;
        E oldValue = elementData(index);
        // 刪除指定元素後,須要左移的元素個數
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // size減一,而後將索引爲size-1處的元素置爲null。爲了讓GC起做用,必須顯式的爲最後一個位置賦null值
        elementData[--size] = null; 
        //從列表中刪除的元素
        return oldValue;
    }

注意:爲了讓GC起做用,必須顯式的爲最後一個位置賦null值。上面代碼中若是不手動賦null值,除非對應的位置被其餘元素覆蓋,不然原來的對象就一直不會被回收。

set( int index, E element)

步驟:

  • 檢查角標
  • 替代元素
  • 返回舊值
/**
     * 用指定的元素替換此列表中指定索引的元素。
     */
    public E set(int index, E element) {
        //對index進行界限檢查
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        //返回原來在這個位置的元素
        return oldValue;
    }

細節:

  • ArrayList是基於動態數組實現的,在增刪時候,須要數組的拷貝複製。
  • ArrayList的默認初始化容量是10,每次擴容時候增長原先容量的一半,也就是變爲原來的1.5倍
  • 刪除元素時不會減小容量,若但願減小容量則調用trimToSize()
  • 它能存放null值。
  • 和 Vector 不一樣,ArrayList 中的操做不是線程安全的!因此,建議在單線程中才使用 ArrayList,而在多線程中能夠選擇 Vector 或者 CopyOnWriteArrayList。

移位運算符   
簡介:移位運算符就是在二進制的基礎上對數字進行平移。按照平移的方向和填充數字的規則分爲三種:<<(左移)、>>(帶符號右移)和>>>(無符號右移)。   
做用:對於大數據的2進制運算,位移運算符比那些普通運算符的運算要快不少,由於程序僅僅移動一下而已,不去計算,這樣提升了效率,節省了資源   
好比這裏:int newCapacity = oldCapacity + (oldCapacity >> 1); 右移一位至關於除2,右移n位至關於除以 2 的 n 次方。這裏 oldCapacity 明顯右移了1位因此至關於oldCapacity /2。
另外須要注意的是:

  • java 中的length 屬性是針對數組說的,好比說你聲明瞭一個數組,想知道這個數組的長度則用到了 length 這個屬性.
  • java 中的length()方法是針對字符串String說的,若是想看這個字符串的長度則用到 length()這個方法.
  • java 中的size()方法是針對泛型集合說的,若是想看這個泛型有多少個元素,就調用此方法來查看!

內部類:

(1)private class Itr implements Iterator<E>  
(2)private class ListItr extends Itr implements ListIterator<E>  
(3)private class SubList extends AbstractList<E> implements RandomAccess  
(4)static final class ArrayListSpliterator<E> implements Spliterator<E>
  • Itr是實現了Iterator接口,同時重寫了裏面的hasNext(),next(),remove()等方法;
  • ListItr繼承Itr,實現了ListIterator接口,同時重寫了hasPrevious(),nextIndex(),previousIndex(),previous(),set(E e),add(E e)等方法,

因此這也能夠看出了 Iterator和ListIterator的區別:ListIterator在Iterator的基礎上增長了添加對象,修改對象,逆向遍歷等方法,這些是Iterator不能實現的。

參考資料:
https://blog.csdn.net/panweiw...
https://segmentfault.com/a/11...
https://github.com/Snailclimb...

相關文章
相關標籤/搜索