第一感覺是一樣的,盲猜后者調用了前者,並傳入參數 0。然而,無論是 JDK 7 還是 JDK 8,這兩個方法構造的結果都是不一樣的。JDK 開發人員在這方面作了優化。
JDK 7
在 Java 7 中,這兩個方法非常簡答,ArrayList(int initialCapacity) 初始化動態數組的長度為指定的 initialCapacity,而 ArrayList() 調用了 ArrayList(int) ,傳入參數 10,初始化了一個長度為 10 的數組。在 Java 7 中,后者確實調用了調用了前者,但是傳入的參數並非 0,而是 10。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
那么,為什么初始化為 10,而不是初始化為 0 呢?初始化為 0 意味着一插入元素就會觸發動態數組的擴容,如果逐個增加元素,容量變化為:0, 1, 2, 3, 4, 6, 9, 13, 19, 28 ...,公式為:newCapacity = oldCapacity + (oldCapacity / 2)。意味着如果初始化為 0,連續插入少量元素會頻繁觸發擴容,而 ArrayList 中插入少量元素是大概率的。所以干脆就直接初始化為 10,減少擴容的開銷。
但是這樣存在一個問題:無論是否使用 ArrayList 實例,只要調用了 new ArrayList(),就會初始化一個長度為 10 的 Object 數組,不管未來是否使用這個實例。JDK 7 的更新版本中和 JDK 8 中采用延遲初始化的策略解決了這個問題。
JDK 8
JDK 開發人員發現大約 80% 的時候 Java 程序員調用 new ArrayList() 來實例化 ArrayList 對象(來源:RFR JDK-7143928)。因此,延遲初始化策略能夠優化大多數使用 ArrayList 的情況。
代碼:
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
代碼中使用了 EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 兩個變量來區分 new ArrayList(0) 和 new ArrayList() 的情況,這兩個成員變量默認值都是空的 Object 數組。在首次擴容的時候,根據 elementData 引用對象的不同來決定最小容量。
// 供用戶使用的 public 確保容量足夠的方法
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // 不是通過 new ArrayLsit() 初始化的
// 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; // 常量,值為 10
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 供 ArrayList 內部使用的 private 確保容量足夠的方法,與上面的方法平行
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 通過 new ArrayList() 來初始化的,最小取 DEFAULT_CAPACITY (值為 10)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ArrayList 實例插入第 1 個元素時,如果實例是通過 new ArrayList(0) 來初始化的,計算出來的 minCapacity 為 0;如果是 new ArrayList() 構造出來的,計算出來的 minCapacity 為 DEFAULT_CAPACITY (值為 10)。然后將 minCapacity 傳給 grow 方法,對數組進行擴容。
// 擴容規則
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);
}
可以用如下圖來表示從構造對象到插入第一個元素時 elementData 所引用對象的變化。
容量變化序列:
| 構造方式 | 容量序列 |
|---|---|
| new ArrayList(0) | 0, 1, 2, 3, 4, 6, 9, 13, 19, 28, 42, 63, ..., Integer.MAX_VALUE - 8 |
| new ArrayList() | 0, 10, 15, 22, 33, 49, 73, 109, 163, 244, 366, ..., Integer.MAX_VALUE-8 |
MAX_ARRAY_SIZE 的值為 Integer.MAX_VALUE - 8
小結
在構造 ArrayList 實例的時候,如果沒有指定動態數組的初始容量,JDK 會自動給動態數組初始化一個容量,以減少插入少量元素時動態數組的擴容開銷。但這樣無論是否使用實例,都會去創建一個長度為 10 的 Object 數組。開發人員發現,絕大多數情況下代碼都是直接通過 new ArrayList() 來構造實例的,於是在新版本的 JDK 中使用延遲初始化的策略優化了這些情況。
在實際開發中,如果已知了 ArrayList 中需要存放的元素的數量,應該調用 new ArrayList(int initialCapacity) 方法來創建對象,這樣可以消除動態數組擴容的開銷,將增加元素的時間復雜度固定為 O(1)。如果存放數量未知,則調用 new ArrayList() 來創建對象,代碼中已經通過設置初始容量、延遲初始化的方式使效率盡可能高。
