1. 程式人生 > >ArrayList工作原理及底層原始碼實現

ArrayList工作原理及底層原始碼實現

一.概述

以陣列實現。節約空間,但陣列有容量限制。超出限制時會增加50%容量,用System.arraycopy()複製到新的陣列。因此最好能給出陣列大小的預估值。預設第一次插入元素時建立大小為10的陣列。

按陣列下標訪問元素-get(i)、set(i,e) 的效能很高,這是陣列的基本優勢。

如果按下標插入元素、刪除元素-add(i,e)、 remove(i)、remove(e),則要用System.arraycopy()來複制移動部分受影響的元素,效能就變差了。

越是前面的元素,修改時要移動的元素越多。直接在陣列末尾加入元素-常用的add(e),刪除最後一個元素則無影響。

ArrayList的原始碼註釋是這樣說的:

* Resizable-array implementation of the <tt>List</tt> interface.  Implements
 * all optional list operations, and permits all elements, including
 * <tt>null</tt>.  In addition to implementing the <tt>List</tt> 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
 * <tt>Vector</tt>, except that it is unsynchronized.)

大致的意思是:
是List 介面的大小可變陣列的實現,實現所有可選的列表操作,並允許所有元素,包括null。除了實現List介面之外,這個類還提供了一些方法來操縱陣列的大小,以便在內部儲存列表。(這個類大致相當於向量,只是它是不同步的。)

二.構造方法

下面是從jdk1.8直接複製下來的ArrayList類的原始碼,我加了一些中文翻譯,和簡單的註釋方便閱讀

    //預設初始化容量
    private static final int DEFAULT_CAPACITY = 10;
 
    //空物件陣列
    private static final Object[] EMPTY_ELEMENTDATA = {};
 
    //物件陣列
    private transient Object[] elementData;
 
    //集合元素個數
    private int size;

     /**
     * Constructs an empty list with the specified initial capacity.
     * 用指定的初始容量構造一個空列表
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        //傳入的容量大於0的情況,建立一個指定長度的空陣列
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        //傳入容量為0的情況,建立一個長度為0的空陣列
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     * 構造一個空列表,初始容量為10。
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     * 構造一個包含指定元素的列表的列表list,按照集合的順序返回迭代器。
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        // 持有傳入集合的內部陣列的引用
        elementData = c.toArray();
        // 如果傳入的集合長度不為0
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            // 判斷引用的陣列型別, 並將引用轉換成Object陣列引用
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            // 如果傳入的集合為0,建立一個空陣列
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

可以看到ArrayList的內部儲存結構就是一個Object型別的陣列,因此它可以存放任意型別的元素。在構造ArrayList的時候,如果傳入初始大小那麼它將新建一個指定容量的Object陣列,如果不設定初始大小那麼它將不會分配記憶體空間而是使用空的物件陣列,在實際要放入元素時再進行記憶體分配。下面再看看它的增刪改查方法。

三.add方法

/**
     * Appends the specified element to the end of this list.
     * 將指定的元素附加到列表的末尾。
     */
    public boolean add(E e) {
        // 新增前先檢查是否需要拓展陣列, 此時陣列長度最小為size+1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 將元素新增到集合的末尾
        elementData[size++] = e;
        return true;
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     * 在指定的位置插入指定的元素。改變當前位置的元素(如果有的話),並移動後邊的所有
     * 後續元素(增加一個到它們的索引)。
     */
    public void add(int index, E element) {
        // 檢查索引是否在指定返回內(0到size之間)
        rangeCheckForAdd(index);
        // 新增前先檢查是否需要拓展陣列, 此時陣列長度最小為size+1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 移動後面元素的位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

我們可以看到:
1.add(e)方法直接在陣列的最後新增元素,操作較快,
2.add(int,e)方法是在指定位置插入元素,需要移動後面的元素,速度較慢,
3.他們的實現其實最核心的內容就是ensureCapacityInternal。這個函式其實就是自動擴容機制的核心。我們依次來看一下他的具體實現

 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

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

        // overflow-conscious code
        // 如果最小容量大於陣列長度就擴增陣列
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
 /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 提高容量,確保至少能保持由最小容量引數指定的元素個數。
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        // //獲取陣列原有的容量
        int oldCapacity = elementData.length;

        // 擴充套件為原來的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        // 如果擴充套件為1.5倍後還不滿足需求,則直接擴充套件為需求值
        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);
    }

我們可以看到:
1.ensureCapacityInternal方法直接呼叫ensureExplicitCapacity方法實現這種最低要求的儲存能力,並傳入了實際儲存存元素的陣列elementData,和需要的最小容量mincapacity,
2.ensureExplicitCapacity方法中,判斷需要最小的容量大於陣列本身容量,就進行擴容呼叫grow方法,
3.grow中先獲取到陣列原有的容量,並擴充套件到原來的1.5倍,再校驗是否滿足,如果不滿足就直接擴充套件的需要的最小容量,最後將原來的陣列拷貝到新的陣列.

四.remove方法

 /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     * 刪除該列表中指定位置的元素,將所有後續元素轉移到前邊(將其中一個元素從它們中減去指數)
     */
    public E remove(int index) {
        // 檢查傳入的下標是否合理
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 將index後面的元素向前挪動一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // GC
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;
    }

remove方法,由於需要將刪除位置後面的元素向前挪動,也會設計陣列複製,所以操作較慢。

五.set/get方法

 /**
     * Returns the element at the specified position in this list.
     * 返回列表中指定位置的元素
     */
    public E get(int index) {
        // index不能大於size
        rangeCheck(index);
        
        // 返回指定位置元素
        return elementData(index);
    }
/**
     * Replaces the element at the specified position in this list with
     * the specified element.
     * 在這個list中替換指定位置的元素為指定的元素。
     */
    public E set(int index, E element) {
        // index不能大於size
        rangeCheck(index);
        
        // 返回指定位置元素
        E oldValue = elementData(index);
        //替換成新元素
        elementData[index] = element;
        return oldValue;
    }

set:直接對指定位置元素進行修改,不涉及元素挪動和陣列複製,操作快速。

get:直接返回指定下標的陣列元素,操作快速。

六.小結

從以上原始碼可以看出

1. ArrayList底層實現是基於陣列的,因此對指定下標的查詢和修改比較快,但是刪除和插入操作比較慢。

2. 構造ArrayList時儘量指定容量,減少擴容時帶來的陣列複製操作,如果不知道大小可以賦值為預設容量10。

3. 每次新增元素之前會檢查是否需要擴容,擴容是增加原有容量的一半,如果擴容之後還不滿足將直接增加到所需要的容量。

4. 每次對下標的操作都會進行安全性檢查,如果出現數組越界就立即丟擲異常。

5. ArrayList的所有方法都沒有進行同步,因此它不是執行緒安全的。

6. 以上分析基於JDK1.8,其他版本會有些出入,因此不能一概而論。

參考資料:

https://mp.weixin.qq.com/s/lln6qnfIXffqPwXvRZBTcQ

https://www.javazhiyin.com/181.html