1. 程式人生 > 其它 >【Java容器】ArrayList擴容機制及原始碼分析

【Java容器】ArrayList擴容機制及原始碼分析

技術標籤:Java容器佇列javaarraylist原始碼

ArrayList原始碼分析及擴容機制


一、ArrayList介紹:

ArrayList 的底層是陣列佇列(Object[]),相當於動態陣列。與 Java 中的陣列相比,它的容量能動態增長。

ArrayList 不保證執行緒安全;

在新增大量元素前,應用程式可以使用ensureCapacity操作來增加 ArrayList 例項的容量。這可以減少遞增式再分配的數量。

建構函式:

    /**
     *預設無參建構函式
     */
    public ArrayList() {
    }
     /**
     * 帶初始容量引數的建構函式     */
	public ArrayList(int initialCapacity) {
    }
    /**
     * 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。
     */
    public ArrayList(Collection<? extends E> c) {
    }


二、ArrayList原始碼分析:

2.1 AL的父親們:


public
class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • RandomAccess 是一個標誌介面(在某些排序和搜尋演算法中會檢查是否是繼承這個標誌介面),表明實現這個這個介面的 List 集合是支援快速隨機訪問的。
  • ArrayList 實現了 Cloneable 介面 ,即覆蓋了函式clone(),能被克隆。
  • ArrayList 實現了 java.io.Serializable 介面,這意味著ArrayList支援序列化,能通過序列化去傳輸。

2.2 AL的初始化,你不用就給你個空的:

	// 初始大小
	private static final int DEFAULT_CAPACITY = 10;
	// 初始空陣列
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	// ArrayList的底層實現陣列
    transient Object[] elementData;
    /**
    	無引數建構函式,可見如果我們沒有給定初始值,AL只是建立了一個空陣列“糊弄人”
    */
        public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

通過上面我們看到了ArrayList無參初始化時並沒有給定陣列容量,其實這裡就牽扯出了ArrayList的擴容機制:


三、ArrayList擴容分析:

3.1 add()方法,你用我我才給你開闢空間:

add()方法:(這裡是我簡化合並後的核心程式碼)

	// AL 的元素個數
    private int size;
    /**
     * 將指定的元素追加到此列表的末尾。
     */
    public boolean add(E e) {
    	int minCapacity = size + 1// 如果是預設初始化的陣列
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 獲取預設的容量和size + 1的較大值,+1是因為add發生時正在存入第一個元素
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 如果add會發生記憶體溢位的情況,第一次add時便會最終進入grow擴容
        if (minCapacity - elementData.length > 0){
        	//呼叫grow方法進行擴容
            grow(minCapacity);
        }
        // 這裡看到ArrayList新增元素的實質就相當於為陣列賦值
        elementData[size++] = e;
        return true;
    }

分析一波:
其實add()方法最核心的判斷就在於if (minCapacity - elementData.length > 0),我再翻譯一下就是if (size + 1 - elementData.length > 0),也就是判斷這次add是否會超過這時AL的容量,如果超過,那麼就進入grow()方法進行擴容,第一次add()方法執行時,因為minCapacity=10 而elementData.length=0所以也會進grow()方法。


3.2 grow() :真正的擴容方法是我:

grow()方法:(同樣是簡化版本)

	// 要分配的最大陣列大小
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    private void grow(int minCapacity) {
        // 舊容量
        int oldCapacity = elementData.length;
        // oldCapacity /2,新容量實際是 oldCapacity * 1.5 
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 然後*1.5都不夠的話,就要多少給多少,直接等於minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新容量大於了AL定義的最大容量,那就交給minCapacity來確定一個合適容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = minCapacity > MAX_ARRAY_SIZE) ?
            	Integer.MAX_VALUE :
            	MAX_ARRAY_SIZE;
        // 複製擴容操作
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

至於最後的copyOf方法內部實際呼叫了 System.arraycopy() 方法來實現陣列的複製,通過複製來擴容。

這是總結:

無參初始容量零,一次add變成10,
1.5倍不夠爽,最小容量來確定,
雖然陣列有上限,動態陣列還是爽