Java原始碼篇之容器類——ArrayList

SGdan_qi發表於2020-05-09

1、前言

對於平常開發的時候遇到的ArrayList,在此做一個簡單的原始碼閱讀記錄,JDK1.8版本。

2、ArrayList的類關係

在這裡插入圖片描述

首先看下ArrayList的類關係圖,可以看到實現了

  • Serializable介面,支援序列化與反序列化;

  • Cloneable介面,可以被克隆;

  • RandomAccess介面,支援隨機訪問,另外fori迴圈會比迭代器迴圈效率高,程式碼如下:

    for (int i=0, n=list.size(); i < n; i++)
             list.get(i); 
    

    比這個迴圈執行得更快:

    for (Iterator i=list.iterator(); i.hasNext(); )
             i.next(); 
    

3、ArrayList的原始碼

一、類的屬性

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

/**
 * (用於空例項的)共享空陣列例項
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * (用於預設大小的空例項的)共享空陣列例項,我們將其與空的元素資料區分開來,
 * 以瞭解何時新增第一個元素.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 儲存ArrayList元素的陣列緩衝區。ArrayList的容量是此陣列緩衝區的長度。任何
 * elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA elementData 
 * 將在新增第一個元素時擴充套件到預設容量(10)。
 */
transient Object[] elementData;		// transient關鍵字標記的成員變數不參與序列化過程

/**
 * ArrayList的大小(包含的元素數)
 */
private int size;

/**
 * 可分配的陣列的最大大小,可能會導致OutOfMemory錯誤:Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

二、add()方法

// 預設往陣列末尾新增元素
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 大小加1
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {		// minCapacity = size + 1
    // elementData 是成員變數
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {/ 如果
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		// 陣列為空時,返回較大的數
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity; // 陣列非空時,返回 minCapacity = size + 1
}

private void ensureExplicitCapacity(int minCapacity) {		// minCapacity = size + 1
    modCount++;		// 計數
    // 陣列元素個數加1之後如果大於當前陣列長度,則進行擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {						// minCapacity = size + 1
    int oldCapacity = elementData.length;					// 舊陣列的長度
    int newCapacity = oldCapacity + (oldCapacity >> 1);		// 新陣列的長度 = 1.5倍舊陣列長度
    // 新陣列長度小於陣列元素個數加1
    if (newCapacity - minCapacity < 0)						
        newCapacity = minCapacity;
    // 新陣列長度大於陣列最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 建立一個新的陣列,並把舊陣列元素複製過去,newCapacity為新陣列大小
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)		
        // 新陣列長度大於陣列最大值,並且minCapacity<0才會丟擲oom錯誤
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

// 指定位置新增元素
public void add(int index, E element) {
    // 下標合法性校驗
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1); 
    // 生成一個index位置為null的新陣列
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

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

三、get()方法

public E get(int index) {
    // 檢查合法性
    rangeCheck(index);	
    // 返回陣列元素
    return elementData(index);
}

private void rangeCheck(int index) {
    // 下標位置大於等於陣列長度的時候(陣列下標從0開始),丟擲下標越界異常
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}

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

// 刪除指定的元素,第一個出現的
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

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
}

五、set()方法

// 將index位置的元素置換成element
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

4、總結

  • 通過閱讀原始碼可以看出,ArrayList的這些方法都沒有加鎖,所以在多執行緒環境下是不安全的;
  • add()和remove()方法都需要建立新的陣列,是比較消耗效能的;

相關文章