1. 程式人生 > 實用技巧 >ArrayList原始碼解析-Java8

ArrayList原始碼解析-Java8

目錄

一.ArrayList介紹

二.ArrayList原始碼分析

  2.1 重要的屬性

  2.2 構造方法

  2.3新增元素

  2.4 陣列擴容

  2.5 刪除元素

  2.6 陣列縮容

  2.7 獲取元素

一.ArrayList介紹

  ArrayList在平時開發過程中使用得特別頻繁,它的底層是使用陣列,存線上程併發安全(併發讀寫);

  與之密切相關的Vector,功能和ArrayList幾乎一樣(原始碼也幾乎一樣),但是Vector是併發安全的,因為Vectory的介面,大多是加了synchronized關鍵字進行同步操作,達到併發安全的效果。

二.ArrayList原始碼分析

2.1 重要的屬性

/**
 * 預設容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 共享的靜態變數(一個空的陣列)
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 預設空容量的陣列
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 儲存ArrayList中元素的真實資料,使用transient表示序列化時不計入該欄位
 */
transient Object[] elementData;

/**
 * 記錄ArrayList中元素的個數
 */
private int size;

/**
 * 允許申請的最大容量
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  

2.2 構造方法

  ArrayList有三個構造方法,需要注意的是,使用無參構造方法來建立ArrayList時,不會建立陣列,也就是說不會分配陣列記憶體空間。程式碼如下:

public ArrayList() {
    // 無參構造方法,直接將儲存資料的elementData設定為空陣列
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(int initialCapacity) {
    // 傳入的初始容量大於0,則會建立指定容量長度的陣列
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 如果傳入的初始容量為0,則指定為空陣列
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 容量小於0,屬於非法引數,直接丟擲異常
        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 {
        // 如果傳入的集合為空,那麼直接設定為空的陣列
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

2.3 新增新元素

  新增新元素,常用的就是追加新元素和在指定位置插入新元素。

  需要注意的是,ArrayList總是在真正插入元素前判斷是否需要擴容,如果需要擴容,則擴容後,再插入新元素;不需要擴容則直接插入元素。

k/**
 * 新增新元素(append)
 *
 * @param e 新元素
 */
public boolean add(E e) {
    // 判斷是否需要擴容(元素數量+1 如果超過陣列容量,則會進行擴容)
    ensureCapacityInternal(size + 1);

    // 將新元素放到最後一個元素後面(同時元素數量加1)
    elementData[size++] = e;
    return true;
}

/**
 * 將新元素插入指定位置
 */
public void add(int index, E element) {
    // 檢測index是否越界
    rangeCheckForAdd(index);

    // 檢測是否需要擴容(若需要,則進行擴容),同時修改modCount+1
    ensureCapacityInternal(size + 1);

    // 將index後面元素統統往後移動一個位置,空出index位置
    System.arraycopy(elementData, index, elementData, index + 1, size - index);

    // 將新元素放入index位置
    elementData[index] = element;

    // 元素數量加1
    size++;
}

/**
 * 檢測指定的index是否越界
 */
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}

2.4 陣列擴容

  前面每次add的時候,都會呼叫ensourCapacityInternal(size+1),這個方法裡面就會判斷是否需要進行擴容,如果需要擴容則會進行擴容操作。

  除此之外,該方法還有另外一個重要的操作,就是modCount++,記錄陣列的變化次數,用於快速失敗。

/**
 * 確保內部陣列的容量滿足minCapacity
 *
 * @param minCapacity 期望的最小容量
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

/**
 * 計算容量(正確容量)
 *
 * @param elementData 儲存元素的陣列
 * @param minCapacity 期望的最小容量
 * @return 確定容量
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果陣列為空陣列,那麼就取minCapacity和預設容量10的較大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    // 陣列不為空,直接返回minCapacity
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    // 記錄陣列的修改次數
    modCount++;

    // 是否minCapacity超過當前陣列的長度(當前容量),證明需要擴容(擴容的時機,重要!!!!)
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity);
    }
}

/**
 * 陣列擴容
 *
 * @param minCapacity 希望擴容後的陣列長度
 */
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;               // 舊陣列的長度
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新陣列的容量(舊陣列的1.5倍)

    // 判斷計算後的新陣列容量是否滿足要求的最小容量
    // 如果不滿足,則直接將新陣列的容量設定為需要的容量
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }

    // 如果申請的容量大於允許申請的最大容量,則返回允許申請的最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }

    // 申請一個新陣列(新容量),然後將原陣列的資料拷貝到新陣列中
    elementData = Arrays.copyOf(elementData, newCapacity);
}

  從原始碼中可以看出,當底層陣列滿的時候(插入新元素,size+1,超過底層陣列的容量),則需要進行擴容,擴容時,會建立1.5倍舊陣列的新陣列,並將舊陣列的元素拷貝到新陣列中(順序不會變)。

2.5 刪除元素

  刪除元素有兩種方式,分別是刪除指定元素和刪除指定下標的元素。

  刪除元素之後,如果刪除的不是最後一個元素,則需要將刪除元素後面的元素往前挪一個位置,並將空出來的最後一個位置置位null(help GC)。

/**
 * 刪除指定位置的元素
 */
public E remove(int index) {
    // 判斷陣列是否越界
    rangeCheck(index);

    // 修改次數加1
    modCount++;

    // 從陣列中返回下標為index的元素
    E oldValue = elementData(index);

    // 計算需要移動的元素數量
    int numMoved = size - index - 1;
    
    // 如果需要移動的元素數量大於0(也就是刪除的元素不是最後一個元素)
    // 則將index+1開始元素一次往前移動一個位置
    if (numMoved > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }
    
    // 將最後一個元素的位置刪除(置位null,讓GC的時候清除對應的記憶體)
    elementData[--size] = null;

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

/**
 * 判斷陣列是否越界
 *
 * @param index 指定陣列下標
 */
private void rangeCheck(int index) {
    if (index >= size) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}

/**
 * 返回陣列中下標為index的元素
 * @param index
 * @return
 */
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

/*--------------------------------------------------------*/

/**
 * 刪除指定元素(遍歷陣列中的元素,刪除第一個匹配的元素)
 *
 * @return true:刪除元素; false:未找到要刪除的元素
 */
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++) {
            // 對於null,直接只用==進行比較
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
        }
    } else {
        for (int index = 0; index < size; index++) {
            // 對於非null的元素,使用equals方法進行比較
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
        }
    }
    
    return false;
}

/**
 * 快速刪除指定下標的元素(不檢測陣列越界,不返回被刪除的元素)
 */
private void fastRemove(int index) {
    // 修改次數加1
    modCount++;

    // 判斷需要移動的元素數量
    int numMoved = size - index - 1;

    // 如果需要移動的元素數量大於0(刪除的不是最後一個元素)
    // 則將index後面的元素一次往前移動
    if (numMoved > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }

    // 將陣列空出的最後一個位置置位null(GC時清除元素),然後陣列元素減1
    elementData[--size] = null; // clear to let GC do its work
}

  

2.6 陣列縮容

  可以思考這種場景,一個ArrayList,剛開始初始容量為0,新增元素後,容量分別調為10、16...1024,但是後期陣列元素在不斷的刪除元素,雖然陣列中只剩下了5個元素,但是陣列的長度是1024,這是極大地浪費,此時需要進行縮容,就是讓底層陣列調整為適合長度,最適合的長度就是剛好和元素數量個數相同。

  ArrayList提供了trimToSize方法,就是進行縮容操作,將底層陣列設定為剛好裝下現有元素的長度

/**
 * 縮容操作,將底層陣列組調整為size長度
 */
public void trimToSize() {
    // 修改次數加1
    modCount++;

    // 如果陣列中的元素數量小於陣列的長度,才進行縮容
    // 如果陣列中沒有元素,則將底層陣列切換為一個空陣列,否則就換為size大小的陣列
    if (size < elementData.length) {
        elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
    }
}

  

2.7 獲取元素

  獲取元素的過程,比較簡單:

/**
 * 返回ArrayList中index下標的元素
 */
public E get(int index) {
    // 陣列越界檢測
    rangeCheck(index);

    // 返回index下標的元素
    return elementData(index);
}

/**
 * 判斷陣列是否越界
 *
 * @param index 指定陣列下標
 */
private void rangeCheck(int index) {
    if (index >= size) {
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}

/**
 * 返回陣列中下標為index的元素
 *
 * @param index
 * @return
 */
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

  原文地址:https://www.cnblogs.com/-beyond/p/13254542.html