Java8源碼-ArrayList

轉自:https://blog.csdn.net/panweiwei1994/article/details/76760238java

頂部註釋

List接口的大小可變數組的實現。實現了全部可選列表操做,並容許包括null在內的全部元素。除了實現List接口外,此類還提供一些方法來操做內部用來存儲列表的數組的大小。(此類大體上等同於Vector類,除了此類是不一樣步的。)算法

size、isEmpty、get、set、iterator和listIterator操做都以固定時間運行。add操做以分攤的固定時間運行,也就是說,添加n個元素須要O(n)時間。其餘全部操做都以線性時間運行(大致上講)。與用於LinkedList實現的常數因子相比,此實現的常數因子較低。數組

每一個ArrayList實例都有一個容量。該容量是指用來存儲列表元素的數組的大小。它老是至少等於列表的大小。隨着向ArrayList中不斷添加元素,其容量也自動增加。並未指定增加策略的細節,由於這不僅是添加元素會帶來分攤固定時間開銷那樣簡單。併發

在添加大量元素前,應用程序可使用ensureCapacity操做來增長ArrayList實例的容量。這能夠減小遞增式再分配的數量。dom

注意,此實現不是同步的。若是多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。(結構上的修改是指任何添加或刪除一個或多個元素的操做,或者顯式調整底層數組的大小;僅僅設置元素的值不是結構上的修改。)這通常經過對天然封裝該列表的對象進行同步操做來完成。若是不存在這樣的對象,則應該使用Collections.synchronizedList方法將該列表「包裝」起來。這最好在建立時完成,以防止意外對列表進行不一樣步的訪問:
List list = Collections.synchronizedList(new ArrayList(…)); 性能

此類的iterator和listIterator方法返回的迭代器是快速失敗的:在建立迭代器以後,除非經過迭代器自身的remove或add方法從結構上對列表進行修改,不然在任什麼時候間以任何方式對列表進行修改,迭代器都會拋出ConcurrentModificationException。所以,面對併發的修改,迭代器很快就會徹底失敗,而不是冒着在未來某個不肯定時間發生任意不肯定行爲的風險。this

注意,迭代器的快速失敗行爲沒法獲得保證,由於通常來講,不可能對是否出現不一樣步併發修改作出任何硬性保證。快速失敗迭代器會盡最大努力拋出ConcurrentModificationException。所以,爲提升這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤的作法:迭代器的快速失敗行爲應該僅用於檢測bug。.net

此類是Java Collections Framework的成員。線程

從上面的內容中能夠總結出如下幾點:code

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

定義

先來看看ArrayList的定義:

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所需的工做。
    • implements List<E>:實現了List。實現了全部可選列表操做。
    • implements RandomAccess:代表ArrayList支持快速(一般是固定時間)隨機訪問。此接口的主要目的是容許通常的算法更改其行爲,從而在將其應用到隨機或連續訪問列表時能提供良好的性能。
    • implements Cloneable:代表其能夠調用clone()方法來返回實例的field-for-field拷貝。
    • implements 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中的元素。
 * ArrayList的容量就是該數組的長度。
 * 該值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA 時,當第一次添加元素進入ArrayList中時,數組將擴容值DEFAULT_CAPACITY。
 * 被標記爲transient,在對象被序列化的時候不會被序列化。
 */
transient Object[] elementData; // non-private to simplify nested class access

/**
 * ArrayList的實際大小(數組包含的元素個數)。
 * @serial
 */
private int size;

 

構造方法

接下來,看ArrayList提供的構造方法。ArrayList提供了三種構造方法。

  1. ArrayList(int initialCapacity):構造一個指定容量爲capacity的空ArrayList。
  2. ArrayList():構造一個初始容量爲 10 的空列表。
  3. ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。

ArrayList( int initialCapacity)

/**
 * 構造一個指定初始化容量爲capacity的空ArrayList。
 *
 * @param  initialCapacity  ArrayList的指定初始化容量
 * @throws IllegalArgumentException  若是ArrayList的指定初始化容量爲負。
 */
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);
    }
}

 ArrayList()

/**
 * 構造一個初始容量爲 10 的空列表。
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

ArrayList(Collection<? extends E> c)

/**
 * 構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。
 *
 * @param c 其元素將放置在此列表中的 collection
 * @throws NullPointerException 若是指定的 collection 爲 null
 */
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有如下核心方法

方法名 時間複雜度
get(int index) O(1)
add(E e) O(1)
add(add(int index, E element)) O(n)
remove(int index) O(n)
set(int index, E element) O(1)

get( int index)

/**
 * 返回list中索引爲index的元素
 *
 * @param  index 須要返回的元素的索引
 * @return list中索引爲index的元素
 * @throws IndexOutOfBoundsException 若是索引超出size
 */
public E get(int index) {
    //越界檢查
    rangeCheck(index);
    //返回索引爲index的元素
    return elementData(index);
}

/**
 * 越界檢查。
 * 檢查給出的索引index是否越界。
 * 若是越界,拋出運行時異常。
 * 這個方法並不檢查index是否合法。好比是否爲負數。
 * 若是給出的索引index>=size,拋出一個越界異常
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * 返回索引爲index的元素
 */
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

 

從代碼中能夠看到,由於ArrayList底層是數組,因此它的get方法很是簡單,先是判斷一下有沒有越界,以後就直接經過數組下標來獲取元素。get方法的時間複雜度是O(1)

add(E e)

/**
 * 添加元素到list末尾.
 *
 * @param e 被添加的元素
 * @return true
 */
public boolean add(E e) {
    //確認list容量,若是不夠,容量加1。注意:只加1,保證資源不被浪費
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

 擴容-ensureCapacity等方法

加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 void ensureCapacityInternal(int minCapacity) {
    // 若elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,則取minCapacity爲DEFAULT_CAPACITY和參數minCapacity之間的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
/**
* 數組容量檢查,不夠時則進行擴容,只供類內部使用
* 
* @param minCapacity 想要的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

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

/**
* 擴容,保證ArrayList至少能存儲minCapacity個元素
* 第一次擴容,邏輯爲newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基礎上增長一半。第一次擴容後,若是容量仍是小於minCapacity,就將容量擴充爲minCapacity。
* 
* @param minCapacity 想要的最小容量
*/
private void grow(int minCapacity) {
    // 獲取當前數組的容量
    int oldCapacity = elementData.length;
    // 擴容。新的容量=當前容量+當前容量/2.即將當前容量增長一半。
    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);
}
/**
* 進行大容量分配
*/
private static int hugeCapacity(int minCapacity) {
    //若是minCapacity<0,拋出異常
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //若是想要的容量大於MAX_ARRAY_SIZE,則分配Integer.MAX_VALUE,不然分配MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;

 

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

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

add( int index, E element)

/**
 * 在制定位置插入元素。當前位置的元素和index以後的元素向後移一位
 *
 * @param index 即將插入元素的位置
 * @param element 即將插入的元素
 * @throws IndexOutOfBoundsException 若是索引超出size
 */
public void add(int index, E element) {
    //越界檢查
    rangeCheckForAdd(index);
    //確認list容量,若是不夠,容量加1。注意:只加1,保證資源不被浪費
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 對數組進行復制處理,目的就是空出index的位置插入element,並將index後的元素位移一個位置
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    //將指定的index位置賦值爲element
    elementData[index] = element;
    //實際容量+1
    size++;
}

 

從源碼中能夠看到,add(E e)有三個步驟:

  1. 越界檢查
  2. 空間檢查,若是有須要進行擴容
  3. 插入元素

越界檢查很簡單

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

 

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

remove( int index)

/**
 * 刪除list中位置爲指定索引index的元素
 * 索引以後的元素向左移一位
 *
 * @param index 被刪除元素的索引
 * @return 被刪除的元素
 * @throws IndexOutOfBoundsException 若是參數指定索引index>=size,拋出一個越界異常
 */
public E remove(int index) {
    //檢查索引是否越界。若是參數指定索引index>=size,拋出一個越界異常
    rangeCheck(index);
    //結構性修改次數+1
    modCount++;
    //記錄索引爲inde處的元素
    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; // clear to let GC do its work

    //返回被刪除的元素
    return oldValue;
}

/**
 * 越界檢查。
 * 檢查給出的索引index是否越界。
 * 若是越界,拋出運行時異常。
 * 這個方法並不檢查index是否合法。好比是否爲負數。
 * 若是給出的索引index>=size,拋出一個越界異常
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

 

看完代碼後,能夠將ArrayList刪除指定索引的元素的步驟總結爲

  1. 檢查索引是否越界。若是參數指定索引index>=size,拋出一個越界異常
  2. 將索引大於index的元素左移一位(左移後,該刪除的元素就被覆蓋了,至關於被刪除了)。
  3. 將索引爲size-1處的元素置爲null(爲了讓GC起做用)。

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

set( int index, E element)

/**
 * 替換指定索引的元素
 *
 * @param 被替換元素的索引
 * @param element 即將替換到指定索引的元素
 * @return 返回被替換的元素
 * @throws IndexOutOfBoundsException 若是參數指定索引index>=size,拋出一個越界異常
 */
public E set(int index, E element) {
    //檢查索引是否越界。若是參數指定索引index>=size,拋出一個越界異常
    rangeCheck(index);

    //記錄被替換的元素
    E oldValue = elementData(index);
    //替換元素
    elementData[index] = element;
    //返回被替換的元素
    return oldValue;
}
相關文章
相關標籤/搜索