ArrayList原始碼分析---擴容機制
一 ArrayList建構函式
我們從原始碼中可以看到, ArrayList共有三個建構函式(包含一個無參建構函式和兩個有參建構函式), 所以預設初始化ArrayList的時候它的值是為{}, 即它的容量是為0的
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
當我們指定容量時, 會呼叫方法, 容量為0時依然返回{}, 容量大於0返回Object[指定容量], 否則拋IllegalArgumentException異常
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
當我們傳入一個集合時, 預設初始化成一個Object[], 陣列的length屬性不為0時, 返回一個呼叫Arrays.copyOf()生成的Object陣列, 否則返回{}
/** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @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(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
以無引數構造方法建立 ArrayList 時,實際上初始化賦值的是一個空陣列。當真正對陣列進行新增元素操作時,才真正分配容量。即向陣列中新增第一個元素時,陣列容量擴為10。下面詳細解析
二 ArrayList擴容機制
1, 先看看add()方法
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
每次會在list的末尾新增新元素, 呼叫 對ArrayList的容量進行處理, 然後對陣列下一個下標進行賦值, 最後我們發現ArrayList新增元素的實質就是對陣列進行賦值!
2, 再看看ensureCapacityInternal() 方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
會計算出一個最小擴容量
3, 看看ensureExplicitCapacity() 方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 當我們要 add 進第1個元素到 ArrayList 時,elementData.length 為0 (因為還是一個空的 list),因為執行了
ensureCapacityInternal()
方法 ,所以 minCapacity 此時為10。此時,minCapacity - elementData.length > 0
成立,所以會進入grow(minCapacity)
方法。 - 當add第2個元素時,minCapacity 為2,此時e lementData.length(容量)在新增第一個元素後擴容成 10 了。此時,
minCapacity - elementData.length > 0
不成立,所以不會進入 (執行)grow(minCapacity)
方法。 - 新增第3、4···到第10個元素時,依然不會執行grow方法,陣列容量都為10。
直到新增第11個元素,minCapacity(為11)比elementData.length(為10)要大。進入grow方法進行擴容。
4, grow()
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 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;
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
// oldCapacity為舊容量,newCapacity為新容量 int oldCapacity = elementData.length; //將oldCapacity 右移一位,其效果相當於oldCapacity /2, //我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麼就把最小需要容量當作陣列的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量大於 MAX_ARRAY_SIZE,進入(執行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE, //如果minCapacity大於最大容量,則新容量則為`Integer.MAX_VALUE`,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 `Integer.MAX_VALUE - 8`。 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);
int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次擴容之後容量都會變為原來的 1.5 倍!
並不是部分人說的2倍或者增加5的容量
grow()
方法 :
- 當add第1個元素時,oldCapacity 為0,經比較後第一個if判斷成立,newCapacity = minCapacity(為10)。但是第二個if判斷不會成立,即newCapacity 不比 MAX_ARRAY_SIZE大,則不會進入
hugeCapacity
方法。陣列容量為10,add方法中 return true,size增為1。 - 當add第11個元素進入grow方法時,newCapacity為15,比minCapacity(為11)大,第一個if判斷不成立。新容量沒有大於陣列最大size,不會進入hugeCapacity方法。陣列容量擴為15,add方法中return true,size增為11。
- 以此類推······
這裡補充一點比較重要,但是容易被忽視掉的知識點:
- java 中的
length
屬性是針對陣列說的,比如說你聲明瞭一個數組,想知道這個陣列的長度則用到了 length 這個屬性. - java 中的
length()
方法是針對字串說的,如果想看這個字串的長度則用到length()
這個方法. - java 中的
size()
方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就呼叫此方法來檢視!
5, hugeCapacity() 方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
如果新容量大於 MAX_ARRAY_SIZE,執行 hugeCapacity()
方法來比較 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大於最大容量,則新容量則為Integer.MAX_VALUE
,否則,新容量大小則為 MAX_ARRAY_SIZE 即為 Integer.MAX_VALUE - 8
。
疑問1: 傳入集合的建構函式裡這句話比較的意義是什麼? 原始碼181行(JDK1.8)
if (elementData.getClass() != Object[].class)
疑問2: hugeCapacity() 中可以發現arrayList 最大容量是 Integer.MAX_VALUE, 那MAX_ARRAY_SIZE(值為Integer.MAX_VALUE-8)的意義是什麼? 原始碼268行
網上說是防止記憶體溢位, 或者說是儲存 Headerwords, 但仍是不解為什麼是減少8, 希望大佬能給予講解