一秧秉、先從 ArrayList 的構(gòu)造函數(shù)說起
ArrayList有三種方式來初始化赖钞,構(gòu)造方法源碼如下:
/**
* 默認初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*默認構(gòu)造函數(shù),使用初始容量10構(gòu)造一個空列表(無參數(shù)構(gòu)造)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 帶初始容量參數(shù)的構(gòu)造函數(shù)妒茬。(用戶自己指定容量)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//初始容量大于0
//創(chuàng)建initialCapacity大小的數(shù)組
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等于0
//創(chuàng)建空數(shù)組
this.elementData = EMPTY_ELEMENTDATA;
} else {//初始容量小于0仰美,拋出異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*構(gòu)造包含指定collection元素的列表迷殿,這些元素利用該集合的迭代器按順序返回
*如果指定的集合為null,throws NullPointerException咖杂。
*/
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;
}
}
細心的同學(xué)一定會發(fā)現(xiàn) :以無參數(shù)構(gòu)造方法創(chuàng)建 ArrayList 時庆寺,實際上初始化賦值的是一個空數(shù)組。當真正對數(shù)組進行添加元素操作時诉字,才真正分配容量懦尝。即向數(shù)組中添加第一個元素時知纷,數(shù)組容量擴為10。 下面在我們分析 ArrayList 擴容時會講到這一點內(nèi)容陵霉!
二琅轧、一步一步分析 ArrayList 擴容機制
這里以無參構(gòu)造函數(shù)創(chuàng)建的 ArrayList 為例分析:
1、先來看 add 方法
/**
* 將指定的元素追加到此列表的末尾踊挠。
*/
public boolean add(E e) {
//添加元素之前乍桂,先調(diào)用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//這里看到ArrayList添加元素的實質(zhì)就相當于為數(shù)組賦值
elementData[size++] = e;
return true;
}
2、再來看看 ensureCapacityInternal() 方法
可以看到 add 方法 首先調(diào)用了ensureCapacityInternal(size + 1)
//得到最小擴容量
private void ensureCapacityInternal(int minCapacity) {
// 判斷是否為一個空數(shù)組
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 獲取默認的容量和傳入?yún)?shù)的較大值 初始為10止毕,或傳入值的
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
當 要 add 進第1個元素時,minCapacity為1漠趁,在Math.max()方法比較后扁凛,minCapacity 為10。
3闯传、ensureExplicitCapacity() 方法
如果調(diào)用 ensureCapacityInternal() 方法就一定會進過(執(zhí)行)這個方法谨朝,下面我們來研究一下這個方法的源碼!
//判斷是否需要擴容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//調(diào)用grow方法進行擴容甥绿,調(diào)用此方法代表已經(jīng)開始擴容了
grow(minCapacity);
}
我們來仔細分析一下:
1.當我們要 add 進第1個元素到 ArrayList 時字币,elementData.length 為0 (因為還是一個空的 list),因為執(zhí)行了 ensureCapacityInternal() 方法 共缕,所以 minCapacity 此時為10洗出。此時,minCapacity - elementData.length > 0 成立图谷,所以會進入 grow(minCapacity) 方法翩活。
2.當add第2個元素時,minCapacity 為2便贵,此時e lementData.length(容量)在添加第一個元素后擴容成 10 了菠镇。此時,minCapacity - elementData.length > 0 不成立承璃,所以不會進入 (執(zhí)行)grow(minCapacity) 方法利耍。
3.添加第3、4···到第10個元素時盔粹,依然不會執(zhí)行g(shù)row方法隘梨,數(shù)組容量都為10。
4.直到添加第11個元素舷嗡,minCapacity(為11)比elementData.length(為10)要大出嘹。進入grow方法進行擴容。
4咬崔、grow() 方法
/**
* 要分配的最大數(shù)組大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ArrayList擴容的核心方法税稼。
*/
private void grow(int minCapacity) {
// oldCapacity為舊容量烦秩,newCapacity為新容量
int oldCapacity = elementData.length;
//將oldCapacity 右移一位,其效果相當于oldCapacity /2郎仆,
//我們知道位運算的速度遠遠快于整除運算只祠,整句運算式的結(jié)果就是將新容量更新為舊容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后檢查新容量是否大于最小需要容量扰肌,若還是小于最小需要容量抛寝,那么就把最小需要容量當作數(shù)組的新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于 MAX_ARRAY_SIZE,進入(執(zhí)行) `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 每次擴容之后容量都會變?yōu)樵瓉淼?1.5 倍左右(oldCapacity為偶數(shù)就是1.5倍,否則是1.5倍左右)剂习! 奇偶不同蛮位,比如 :10+10/2 = 15, 33+33/2=49。如果是奇數(shù)的話會丟掉小數(shù).
我們再來通過例子探究一下grow() 方法 :
當add第1個元素時鳞绕,oldCapacity 為0失仁,經(jīng)比較后第一個if判斷成立,newCapacity = minCapacity(為10)们何。但是第二個if判斷不會成立萄焦,即newCapacity 不比 MAX_ARRAY_SIZE大,則不會進入 hugeCapacity 方法冤竹。數(shù)組容量為10楷扬,add方法中 return true,size增為1。
當add第11個元素進入grow方法時贴见,newCapacity為15烘苹,比minCapacity(為11)大,第一個if判斷不成立片部。新容量沒有大于數(shù)組最大size镣衡,不會進入hugeCapacity方法。數(shù)組容量擴為15档悠,add方法中return true,size增為11廊鸥。
以此類推······
這里補充一點比較重要,但是容易被忽視掉的知識點:
①java 中的 length 屬性是針對數(shù)組說的,比如說你聲明了一個數(shù)組,想知道這個數(shù)組的長度則用到了 length 這個屬性.
②java 中的 length() 方法是針對字符串說的,如果想看這個字符串的長度則用到 length() 這個方法.
③java 中的 size() 方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調(diào)用此方法來查看!
5辖所、hugeCapacity() 方法
從上面 grow() 方法源碼我們知道: 如果新容量大于 MAX_ARRAY_SIZE,進入(執(zhí)行) hugeCapacity() 方法來比較 minCapacity 和 MAX_ARRAY_SIZE惰说,如果minCapacity大于最大容量,則新容量則為Integer.MAX_VALUE缘回,否則吆视,新容量大小則為 MAX_ARRAY_SIZE 即為 Integer.MAX_VALUE - 8典挑。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//對minCapacity和MAX_ARRAY_SIZE進行比較
//若minCapacity大,將Integer.MAX_VALUE作為新數(shù)組的大小
//若MAX_ARRAY_SIZE大啦吧,將MAX_ARRAY_SIZE作為新數(shù)組的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}