ArrayList原始碼解析

小碼code發表於2022-06-28

在平時Java,儲存資料需要用到列表,而大多時候都能用到ArrayList,比如Mybatis查詢資料列表,返回列表都是ArrayList,很多資料的存放也用到了ArrayList

jdk 版本: 1.8

ArrayList 是基於大小可變的陣列實現,並允許新增null值, 根據下標就能資料查詢快。陣列一旦初始化就確定好了大小,如果需要新增資料,就需要做資料的複製,這些操作比較耗時。

陣列拷貝

ArrayList陣列的擴容、新增和刪除需要用到陣列的拷貝,主要用到了以下兩個方法:

  • System.arraycopy
  • Arrays.copyOf

System.arraycopy

System.arraycopy()Java的原生的靜態方法,該方法將源陣列元素拷貝到目標陣列中去。

引數詳解:

  • src 源陣列
  • srcPos 源陣列拷貝的起始索引
  • dest 目標陣列
  • destPos 拷貝到目標陣列的起始索引
  • length 拷貝元素的個數
    src源陣列,起始索引為srcPos,個數為length,複製到起始索引為destPosdest目標陣列。

比如:

int[] srcArray = new int[]{1,2,3,4,5,6};
int[] desArray = new int[5];
System.arraycopy(srcArray,1,desArray,2,3);
System.out.println("desArray: " + Arrays.toString(desArray));

輸出:

[0, 0, 2, 3, 4]

Arrays.copyOf

Arrays.copyOf本質也是呼叫System.arraycopy方法:

 public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

首先建立一個新的陣列copy,將original陣列全部複製給copy陣列。

主要欄位:

// 底層陣列
transient Object[] elementData;
// 陣列個數大小
private int size;
// 預設空陣列
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  • ArrayList是基於陣列的一個實現,elementData就是底層的陣列。
  • size 是記錄陣列的個數。

構造方法

ArrayList()

public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

無參構造方法,建立資料直接賦值一個空陣列

ArrayList(int initialCapacity)

賦一個初始化容量initialCapacity:

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);
    }
}
  • 初始化容量initialCapacity大於零,新建長度initialCapacity的陣列。
  • 初始化容量initialCapacity等於零,新建一個空陣列。

新增資料

新增資料有兩個方法:

  • add(E e) 直接新增在陣列尾部。
  • add(int index, E element) 根據下標新增資料。

add(E e)

在列表尾部新增資料:

/**
 * Appends the specified element to the end of this list.
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal 判斷新增資料後的容量是否足夠,如果不夠,做擴容操作。

private void ensureCapacityInternal(int minCapacity) {
  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;
}

private void ensureExplicitCapacity(int minCapacity) {
  modCount++;

  // overflow-conscious code
  if (minCapacity - elementData.length > 0)
      grow(minCapacity);
}

ensureCapacityInternal 作用是判斷容量當前容量是否足夠存放新的資料,如果不夠就做擴容操作。

calculateCapacity計算容量,如果陣列為null,返回minCapacity10的最大值,否則返回minCapacity。這個方法就是返回陣列的大小。

呼叫無參構造方法ArrayList(),再呼叫add()方法,首先陣列容量會變成10。這也是為什麼無參構造方法ArrayList()上面有會有註解Constructs an empty list with an initial capacity of ten.

ensureExplicitCapacity 判斷需要的容量minCapacity大於當前陣列的長度elementData.length,將會做擴容處理,也是呼叫grow方法:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 新長度是原來長度的 1.5倍
    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);
}

group 主要做了兩件事:

  • 長度擴大到原來的 1.5倍
  • 拷貝陣列到新長度的陣列

做完判斷是否要做擴容之後,直接在size位置上賦值要新增的元素,然後size再自增。

elementData[size++] = e;

add(E e)流程總結

  • 判斷資料容量是否足夠,如果是空陣列,返回10,其他容量返回當前陣列size + 1
  • 返回容量和當前陣列長度對比,小於做擴容處理。
  • 擴容長度為原來長度的1.5倍,將陣列賦值給長度為原來陣列1.5倍的新陣列。
  • 在陣列的最末尾size賦值。

add(int index, E element)

此新增資料是在指定的下標新增資料。

public void add(int index, E element) {
    // 下標是否越界
    rangeCheckForAdd(index);
    // 判斷是否要擴容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 複製i到size的資料到i+1 到size +1
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

add(int index, E element)index下標新增資料,

  • 首先判斷index 是否在0 ~size之間。
  • 判斷是否要擴容,需要擴容,進行1.5倍擴容。
  • 資料遷移,把index以後的資料全部往後移動一位。
  • 賦值index 下標資料。

獲取資料

獲取資料只有通過陣列下標獲取資料 get(int index)

public E get(int index) {
  //檢查是否超過陣列範圍 
  rangeCheck(index);
  
  return elementData(index);
}

@SuppressWarnings("unchecked")
E elementData(int index) {
    // 通過下標獲取資料 
    return (E) elementData[index];
}

這裡獲取資料就比較簡單:

  • 檢查下標是否超過陣列範圍。
  • 通過下標訪問資料。

刪除資料

remove(Object o)

刪除列表中第一個匹配的元素:

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

判斷要刪除資料是否為null:

  • 為空,判斷elementData[index]是否為空。
  • 不為空,判斷元素equals是否匹配elementData[index]

上面判斷都為true時,呼叫fastRemove方法:

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
}

移動陣列資料,index+1 以及之後的資料往前移動一位,最後一位size-1賦值為null

remove(int index)

根據index下標刪除資料。

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

總結

  • 陣列的擴容、新增和刪除都需要用到System.arraycopy方法,System.arraycopy是將src源陣列,起始索引為srcPos,個數為length,複製到起始索引為destPosdest目標陣列。
  • ArrayList主要是基於Object[] elementData動態陣列實現。
  • 呼叫構造方法,無參的就賦值一個空陣列,有初始容量的就賦值一個初始容量。
  • add 新增陣列首先判斷是否需要擴容,需要擴容,長度擴大成原來的1.5倍。
    • add(E e) 在size賦值新增資料
    • add(int index, E element) 將陣列從index往後移動一位,新資料新增到index上。
  • 獲取資料get(int index)利用陣列的特定,根據下標獲取資料。
  • remove(int index)index之後的資料全部往前移動一位,size - 1賦為null

相關文章