Java容器類框架分析(1)ArrayList原始碼分析

wustor發表於2019-01-28

概述

在分析ArrayList原始碼之前,先看一下ArrayList在資料結構中的位置,常見的資料結構按照邏輯結構跟儲存結構如下:

資料結構分類
資料結構分類

先看看原始碼的註釋:

  • Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including
    null. In addition to implementing the List interface,
    this class provides methods to manipulate the size of the array that is
    used internally to store the list. (This class is roughly equivalent to
    Vector, except that it is unsynchronized.)
  • 實現了List介面的可調整大小的陣列,也就是經常說的動態陣列,實現了所有的可選的列表的操作,並且能夠插入任何元素,包括空值。除了實現了List介面之外,對於內部儲存的陣列,也提供了相應的改變陣列大小的方法。(這個類除了不是執行緒安全的,幾乎可以相當於Vector)

很明顯,ArrayList是一個線性結構,底層通過陣列實現,並且是動態陣列。

下面看一下ArrayList的繼承關係,這個是通過IDEA生成的繼承關係圖,很清晰,終於不用自己畫了。

ArrayList繼承關係
ArrayList繼承關係

通過圖可以看到ArrayList繼承自AbstractList,並且實現了List, RandomAccess, Cloneable, Serializable介面,而AbstractList實現了Collection介面,則ArrayList具有Collection的所有方法,而且能夠進行clone,序列化,下面開始開始對ArrayList的原始碼進行分析,吼吼。

正文

成員變數

//序列化ID
 private static final long serialVersionUID = 8683452581122892189L;
//預設的初始化陣列的容量為10
 private static final int DEFAULT_CAPACITY = 10;
//共享的空陣列,也就是呼叫空的構造方法的時候會進行預設用這個陣列進行初始化
private static final Object[] EMPTY_ELEMENTDATA = {};
//陣列緩衝區,也就是從來存放List元素的陣列。ArrayList的容量就是緩衝區陣列的長度。對於一個空陣列,在新增第一個元素的時候,List的容量會被設定成預設的DEFAULT_CAPACITY,也就是10,
transient Object[] elementData;
//elementData的長度
private int size;複製程式碼

構造方法

構造一個空陣列,採用預設容量(Constructs an empty list with an initial capacity of ten)

  public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

     private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }複製程式碼

前面有提到過,當初始化陣列為空的時候,在add的時候會進行判斷,如果陣列的長度為0,陣列的長度就是預設的陣列長度

構造一個空的陣列,自定義陣列容量(Constructs an empty list with the specified initial capacity)

  public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }複製程式碼

這個跟上面的區別在於建立了一個新的陣列,陣列的最大長度就是傳入的初始化容量,陣列的長度為0。

傳入一個集合進行初始化(Constructs a list containing the elements of the specified collection, in the order they are returned by the collection`s iterator.)

 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }複製程式碼

陣列的長度就是傳入的集合的長度,將集合傳入緩衝陣列

Add方法

在末尾新增一個元素(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;
    }複製程式碼

在指定位置新增一個元素(Inserts the specified element at the specified position in this list.)

 public void add(int index, E element) {
 //判斷index是否合乎規範
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//檢查是否需要擴容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
//拷貝陣列
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
//進行賦值
        elementData[index] = element;
//將陣列的長度自增
        size++;
    }複製程式碼

在末尾新增一個集合(Appends all of the elements in the specified collection to the end of this list)

 public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }複製程式碼

在指定位置新增集合(Inserts all of the elements in the specified collection into this list, starting at the specified position)

  public boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }複製程式碼

Add操作有如上幾個過載方法,仔細觀察一下,大同小異,我們選擇第二個過載方法,也就是在指定位置新增一個元素:

  1. 判斷index是否合理
  2. 檢查是否需要擴容
  3. 拷貝陣列
  4. 進行賦值
  5. 將陣列的長度自增

這裡面比較有營養的就是第二步了,下面重點分析第二步:

呼叫ensureCapacityInternal(size++1)

   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }複製程式碼

傳入了此時陣列需要的長度,只要陣列為初始化的時候沒有指定容量,就會在minCapacity(size+1)跟DEFAULT_CAPACITY中間取一個最大值,之所以這樣,是因為如果將陣列的容量設定成太小,會導致陣列頻繁的擴容,影響效能。

呼叫ensureExplicitCapacity(minCapacity)

    private void ensureExplicitCapacity(int minCapacity) {
    //這個變數來自於ArrayList的父類AbstractList,主要用來記錄集合的操作次數
        modCount++;
        // overflow-conscious code
        //如果此時需要的陣列長度大於陣列本身的長度,則進行擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }複製程式碼

執行擴容操作grow(minCapacity)

   private void grow(int minCapacity) {
        // overflow-conscious code
        //陣列當前的容量
        int oldCapacity = elementData.length;
        //擴容後新陣列的容量,增大了0.5倍
        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);
    }複製程式碼

Remove方法

remove指定位置元素(Removes the element at the specified position in this list)

  public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) 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;
    }複製程式碼

移除某個具體元素(Removes the first occurrence of the specified element from this list,if it is present. If the list does not contain the element, it is unchanged)

    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
    }複製程式碼

仔細觀察一下上面兩個過載方法,其實是差不多的,刪除元素分兩步走:

  1. 找到這個元素
  2. 執行刪除操作

比較簡單,不過多描述

Set方法

    public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }複製程式碼

直接替換掉對應的元素

查詢索引操作

尋找元素第一次出現的下標(Returns the index of the first occurrence of the specified element)

  public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }複製程式碼

尋找最後出現的某個元素的索引(Returns the index of the last occurrence of the specified element in this list)

   public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }複製程式碼

這些都是通過遍歷對元素進行查詢,一個是從頭到尾遍歷,一個是從未到頭遍歷,查到結構就返回對應的下表,否則返回-1.

Get方法

   public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }複製程式碼

總結

ArrayList的特點

  • 有序的,元素可以重複
  • 查詢快,增刪慢
  • 當容量不夠的時候,會進行擴容至當前size的1.5倍
  • 非執行緒安全

關於ArrayList的原始碼就分析到這裡,因為底層的實現是陣列,所以不管是擴容還是其它的增刪改查操作都是對陣列進行操作,所以只要對陣列足夠了解,基本上還是挺好理解的。

相關文章