ArrayList可以實現容量的自適應的增加(As elements are added to an ArrayList,
its capacity grows automatically)。通過JDK1.8中ArrayList的源碼來分析:
ArrayList的相關定義
ArrayList底層采用Object類型的數組實現。
首先,定義默認初始容量,為10
private static final int DEFAULT_CAPACITY = 10;
再有,定義用于空實例的空數組實例
private static final Object[] EMPTY_ELEMENTDATA = {};
再有,定義空數組實例,用于默認大小的空實例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
再有,定義了一個未被序列化的數組elementData,用來存儲ArrayList對象列表
/**
* The capacity of the ArrayList is the length of this array buffer.
*/
transient Object[] elementData;
再有,定義了ArrayList的size變量
/**
* The size of the ArrayList (the number of elements it contains).
*/
private int size;
為了實現ArrayList的自動擴容機制,java引進了capacity和size概念,以區別數組的length。capacity是底層數組的長度(length),size是存儲的列表對象的數量,被設置為private的。
ArrayList的初始化
ArrayList有三種方式來初始化,構造方法源碼如下:
1.用指定的初始容量構造一個空列表
/**
* Constructs an empty list with the specified initial capacity.
*/
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);
}
}
2.使用初始容量10構造一個空列表(無參數構造)
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3.構造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回
如果指定的集合為null,throws NullPointerException。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//用Collection初始化數組elementData
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。當真正對數組進行添加時,才真正分配容量。即向數組中添加第一個元素時,數組容量擴為10。
1.以add(E e)方法為入口分析
public boolean add(E e) {
//添加元素之前,先調用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//添加對象時,自增size
return true;
}
2.進入到ensureCapacityInternal方法
ensureCapacityInternal(“確保內部容量”)。
private void ensureCapacityInternal(int minCapacity) {
//先判斷數組是否為空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
//如果數組為空,將DEFAULT_CAPACITY(為10)和minCapacity中較大的一個賦值給minCapacity
//則minCapaciy至少為10
}
ensureExplicitCapacity(minCapacity);
//執行ensureExplicitCapacity方法
}
當空列表add()第1個元素時,調試可知:原本minCapacity為1,在Math.max()方法比較后,minCapacity為10。
但當add()第2個元素時,數組非空,直接進入ensureExplicitCapacity(minCapacity)(此時minCapacity為2)。
3.進入ensureExplicitCapacity方法
無論數組是否為空,都會進入ensureExplicitCapacity方法。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//定義于ArrayList的父類AbstractList,用于存儲結構修改次數
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//如果數組的長度(elementData.length)小于最小需要的容量(minCapacity),就擴容
grow(minCapacity);
}
當要add第1個元素時,這時elementData.length(為0,因為此時還是空列表)是要比minCapacity(為10)小的,會進入grow(minCapacity)方法(minCapacity為10)。
當add第2個元素時,調試可知:minCapacity為2,此時elementData.length(即是容量)在添加第一個元素后擴容成10了,比minCapacity大,不會去執行grow方法 。數組容量仍為10。
所以當添加第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;
//用來分配數組的size最大值
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
//右移一位相當于原數除以2,位運算的計算方式更快
//所以新容量擴大為原容量的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//從minCapacity和這個newCapacity中取較大值作為擴容后的新數組長度(新容量)。
//這主要是針對添加第1個元素。1.5倍oldCapacity為0,所以newCapacity為最小容量10
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//如果新容量大于數組的最大size,進入hugeCapacity方法
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
//最后,將原來數組的數據復制到新的數組中。
}
當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。
以此類推······
5.如果新容量大于MAX_ARRAY_SIZE,進入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;
//對minCapacity和MAX_ARRAY_SIZE進行比較
//若minCapacity大,將Integer.MAX_VALUE作為新數組的大小
//若MAX_ARRAY_SIZE大,將MAX_ARRAY_SIZE作為新數組的大小
}
所以,像ArrayList添加對象時,在確定好新數組的大小后,會調用Arrays.copyOf()方法,以適當長度(newCapacity)新建一個原數組的拷貝,并修改原數組,指向這個新建數組。java垃圾回收機制會自動回收原數組。這樣會消耗一定的資源。
因此,在初始化ArrayList時,最好可以估算一個初始大小。
6.最好在add大量元素之前用ensureCapacity方法,以減少增量從新分配的次數。 源碼如下:
/**
* <p>An application can increase the capacity of an <tt>ArrayList</tt> instance
* before adding a large number of elements using the <tt>ensureCapacity</tt>
* operation. This may reduce the amount of incremental reallocation.
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}