關於ArrayList的擴容機制
ArrayList作為List接口常用的一個實現類,其底層數據接口由數組實現,可以保證O(1)
復雜度的隨機查找, 在增刪效率上不如LinkedList,但是在查詢效率較高,相對同是數組實現的Vector,並不能保證線程安全,所以多適用於單線程環境。
由於底層是有數組實現,因為數組的長度需要初始化定義,並不能自動進行長度增加,所以ArrayList有對應的擴容機制,當增加元素時,會判斷是否需要擴容,下面看源碼:
首先認識ArrayList的幾個重要變量:
/*序列化ID*/ private static final long serialVersionUID = 8683452581122892189L; /*默認的數組容量,第一次進行添加操作時,如果ArrayList為空或者添加元素后的數組長度小於等於10,會將該值作為數組的初始長度*/ private static final int DEFAULT_CAPACITY = 10; /*一個空的對象數組,在需要將elementData數組置空時,會將該對象賦予elementData*/ private static final Object[] EMPTY_ELEMENTDATA = {}; /*默認的空對象數組,在ArrayList構造時,默認將該對象給elementData*/ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /*ArrayList內部用於存儲元素的數組,transient關鍵字修飾,不使用默認序列化*/ transient Object[] elementData; // non-private to simplify nested class access /*數組的長度,是ArrayList中實際元素的個數,不是數組的容量*/ private int size;
在ArrayList的尾部插入和其他位置雖然是不同方法,但是都使用到了ensureCapacityInternal()方法確保數組內部容量,在每次添加操作中都會使用該方法進行容量判斷,之后,才會將增加的元素添加到數組中,下面以尾部插入為例;
/** * 增加數據元素到集合得末尾 * */ public boolean add(E e) { /*判斷是否擴容,如果原來的元素個數是size,那么增加一個元素之后的元素個數為size + 1,所以需要的最小容量就為size + 1*/ ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
ensureCapacityInternal()將判斷委托給ensureExplicitCapacity()處理
/*獲取數組最小容量*/ private static int calculateCapacity(Object[] elementData, int minCapacity) { /*如果elementData為空,且minCapacity <= 10,都會以DEFAULT_CAPACITY作為最小容量*/ if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureCapacityInternal(int minCapacity) { /*ensureCapacityInternal方法委托給ensureExplicitCapacity方法*/ ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++; /*如果minCapacity大於elementData的長度,使用grow方法進行擴容*/ if (minCapacity - elementData.length > 0) grow(minCapacity); }
下面就是擴容的實現方法grow方法:
/*擴容方法*/ private void grow(int minCapacity) { /*原有數組容量*/ int oldCapacity = elementData.length; /*新的數組容量,下面位運算相當於newCapacity = oldCapacity * 1.5 向下取整*/ int newCapacity = oldCapacity + (oldCapacity >> 1); /*如果新的數組容量小於需要的最小容量,即假設新的數組容量是15,最小需要16的容量,則會將16賦予newCapacity*/ if (newCapacity - minCapacity < 0) newCapacity = minCapacity; /*變量MAX_ARRAY_SIZE = 2147483639 [0x7ffffff7],如果擴容后的新容量大於這個值則會使用hugeCapacity方法 * 判斷最小容量minCapacity是否大於MAX_ARRAY_SIZE,如果需要最小容量的也大於MAX_ARRAY_SIZE,則會以 * Integer.MAX_VALUE = 2147483647 [0x7fffffff]的值最為數組的最大容量,如果沒有則會以MAX_ARRAY_SIZE最為最大容量
* MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,為什么用MAX_ARRAY_SIZE ,源碼的中的說法是一些虛擬機中會對數組保留一些標題字段
* 使用Integer.MAX_VALUE會造成內存溢出錯誤 * */ if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); /*確定數組最終的容量newCapacity之后,將原有ArrayList的元素全部拷貝到一個新的ArrayList中*/ elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { /*如果minCapacity小於0,則拋出內存溢出錯誤*/ if (minCapacity < 0) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
ArrayList的擴容機制還是相對容易理解的,就是在第一個添加元素時,創建一個長度為10的數組,之后隨着元素的增加,以1.5倍原數組的長度創建一個新數組,即10, 15, 22, 33,。。這樣序列建立,將原來的元素拷貝到新數組之中,如果數組長度達到上限,則會以
MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE作為最大長度,而多余的元素就會被舍棄掉。