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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章