關(guān)于ArrayList的動態(tài)擴容機制解讀
1. 前言
對于 ArrayList 的動態(tài)擴容機制想必大家都聽說過,之前的文章中也談到過,不過由于時間久遠,早已忘卻。
所以利用這篇文章做做筆記,加深理解記憶
2. ArrayList 的動態(tài)擴容機制
要了解其動態(tài)擴容機制就必須先看它的源碼,源碼是基于 jdk 1.8 的
2.1. ArrayList 的主要屬性
// 如果不指定容量(空構(gòu)造器),則在添加數(shù)據(jù)時的空構(gòu)造器默認初始容量最小為 10
private static final int DEFAULT_CAPACITY = 10;
// 出現(xiàn)在需要用到空數(shù)組的地方,其中一處是使用自定義初始容量構(gòu)造方法時候如果你指定初始容量為0的時候,那么elementData指向該數(shù)組
// 另一處是使用包含指定collection集合元素的列表的構(gòu)造方法時,如果被包含的列表中沒有數(shù)據(jù),那么elementData指向該數(shù)組
private static final Object[] EMPTY_ELEMENTDATA = {};
// 如果使用默認構(gòu)造方法,那么elementData指向該數(shù)組
// 在添加元素時會判斷是否是使用默認構(gòu)造器第一次添加,如果是數(shù)組就會擴容至10個容量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 默認未初始化的儲存 ArrayList集合元素的底層數(shù)組,其長度就是 ArrayList的容量
transient Object[] elementData;
// 私有的elementData數(shù)組中具體的元素對象的數(shù)量,可通過size方法獲得。默認初始值為0,在add、remove等方法時size會改變
private int size;可以看到 ArrayList 底層核心是一個 Object[] elementData 的數(shù)組
2.2. ArrayList 的構(gòu)造器
// 默認的構(gòu)造器
public ArrayList() {
// Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} 空數(shù)組
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 自定義容量大小的構(gòu)造器
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);
}
}從上面的構(gòu)造器中,可以得出以下結(jié)論
- 如果使用 ArrayList 的默認構(gòu)造器時,它的初始容量就是 0
- 如果使用 ArrayList 的有參構(gòu)造器時,它的初始容量就是你傳入的參數(shù) initialCapacity的值
2.3. ArrayList 的動態(tài)擴容
根據(jù)上面的兩個結(jié)論,不管是使用默認或有參構(gòu)造器時,我們可以使其初始容量為 0,那么它的動態(tài)擴容發(fā)生在哪里?
可以肯定就發(fā)生在 add() 方法中,那么查看 add() 方法源碼如下
public boolean add(E e) {
// ensureCapacityInternal() 如下
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}按照我們第一次 add, size 肯定是 0 了,所以 ensureCapacityInternal 這個函數(shù)傳入的是 1
// 第一次 add 時,參數(shù) minCapacity = 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// calculateCapacity() 方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是第一次 add 元素
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// minCapacity 設(shè)置為 DEFAULT_CAPACITY 與 minCapacity 的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// ensureExplicitCapacity() 方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}elementData.length 是數(shù)組長度,它是隨著數(shù)組擴容而動態(tài)變化的
- 如果是第一次 add 元素時,它為 0
- 之后它是隨著 oldCapacity + (oldCapacity >> 1) 而動態(tài)變化的
首先看 calculateCapacity() 方法
- 如果是第一次 add 元素,那么 minCapacity 設(shè)置為 DEFAULT_CAPACITY 與 minCapacity 的最大值,即 10 與 1 取最大值,這里返回最大值 10
- 否則,直接返回 minCapacity
再看 ensureExplicitCapacity() 方法
- 如果是第一次 add 元素,由上面方法可知 minCapacity = 10,即 10 - 0 > 0 返回 true,再調(diào)用 grow() 方法
- 只要 minCapacity - elementData.length > 0 為 true,就會再調(diào)用 grow() 方法
private void grow(int minCapacity) {
// 原容量
int oldCapacity = elementData.length;
// 擴容后的容量,擴大到原容量的 1.5 倍左右,右移一位相當于原數(shù)值除以 2 的商
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量減去最小容量的值小于 0
if (newCapacity - minCapacity < 0)
// 新容量等于最小容量
newCapacity = minCapacity;
// 如果新容量減去建議最大容量的值大于 0
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 調(diào)整新容量上限或者拋出 OutOfMemoryError
newCapacity = hugeCapacity(minCapacity);
// 最終進行新數(shù)組的構(gòu)建和重新賦值,此后原數(shù)組被摒棄
// elementData:原數(shù)組; newCapacity:新數(shù)組容量
elementData = Arrays.copyOf(elementData, newCapacity);
}elementData.length 是數(shù)組長度,它是隨著數(shù)組拷貝而動態(tài)變化的
- 如果是第一次 add 元素時,它為 0
- 之后它是隨著 oldCapacity + (oldCapacity >> 1) 而動態(tài)變化的
如果是第一次 add 元素,由上面的方法可知參數(shù) minCapacity = 10,第一次 add 元素 oldCapacity = 0,可得知 newCapacity = 0 + 0,由于 newCapacity - minCapacity < 0,所以 newCapacity = minCapacity = 10 重新賦值,最終進行新數(shù)組的構(gòu)建和拷貝賦值
3. 小結(jié)
3.1. 初始容量
如果使用 ArrayList 的默認無參構(gòu)造器時,它的初始容量就是 0
如果使用 ArrayList 的有參構(gòu)造器時,它的初始容量就是你傳入的參數(shù) initialCapacity的值
3.2. 動態(tài)擴容大小
ArrayList 的初始容量為 0,當?shù)谝淮?add 元素時,才會發(fā)生擴容操作,它的擴容大小是 10
當?shù)谝淮?add 元素完成后,此時的 elementData.length = 10,后面要想發(fā)生擴容操作,必須 minCapacity - elementData.length > 0 為 true,即 minCapacity 最小為 11,也就是說當 ArrayList 第十一次 add 時,才會接著發(fā)生擴容操作,且擴容大小為 15 = 10 + 5
3.3. 動態(tài)擴容大小測試
public class TestArrayList {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
list.add(11);// 打上斷點
}
}add() 方法打上斷點

ensureCapacityInternal() 方法打上斷點

ensureExplicitCapacity() 方法打上斷點

grow() 方法打上斷點

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java多線程編程之InheritableThreadLocal
這篇文章主要為大家詳細介紹了java多線程編程之InheritableThreadLocal,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
Spring Boot中Elasticsearch的連接配置原理與使用詳解
在Spring Boot中,我們可以通過Elasticsearch實現(xiàn)對數(shù)據(jù)的搜索和分析,本文將介紹Spring Boot中Elasticsearch的連接配置、原理和使用方法,感興趣的可以了解一下2023-09-09
java ArrayBlockingQueue阻塞隊列的實現(xiàn)示例
ArrayBlockingQueue是一個基于數(shù)組實現(xiàn)的阻塞隊列,本文就來介紹一下java ArrayBlockingQueue阻塞隊列的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2024-02-02
dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter
這篇文章主要介紹了dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03

