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

ensureCapacityInternal() 方法打上斷點(diǎn)

ensureExplicitCapacity() 方法打上斷點(diǎn)

grow() 方法打上斷點(diǎn)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
sun?unsafe類(lèi)功能及使用注意事項(xiàng)詳解
這篇文章主要為大家介紹了unsafe類(lèi)的功能及在使用中需要注意的事項(xiàng)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-01-01
Java多線程 Guarded Suspension設(shè)計(jì)模式
這篇文章主要介紹了Java多線程 Guarded Suspension設(shè)計(jì)模式,Guarded Suspension意為保護(hù)暫停,其核心思想是僅當(dāng)服務(wù)進(jìn)程準(zhǔn)備好時(shí),才提供服務(wù),文章圍繞Java多線程 Guarded Suspension展開(kāi)內(nèi)容,需要的朋友可以參考一下2021-10-10
mybatis連接PGSQL中對(duì)于json和jsonb的處理方法
在使用PostgreSQL數(shù)據(jù)庫(kù)時(shí),將表字段設(shè)置為jsonb格式可以存儲(chǔ)JSON數(shù)據(jù),本文給大家介紹mybatis連接PGSQL中對(duì)于json和jsonb的處理方法,感興趣的朋友一起看看吧2024-11-11
Springboot2以代碼的方式統(tǒng)一配置Jackson教程
這篇文章主要介紹了Springboot2以代碼的方式統(tǒng)一配置Jackson教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Java?Float?保留小數(shù)位精度的實(shí)現(xiàn)
這篇文章主要介紹了Java?Float?保留小數(shù)位精度的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
解決Mybatis-plus找不到對(duì)應(yīng)表及默認(rèn)表名命名規(guī)則的問(wèn)題
這篇文章主要介紹了解決Mybatis-plus找不到對(duì)應(yīng)表及默認(rèn)表名命名規(guī)則的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
Spring如何基于xml實(shí)現(xiàn)聲明式事務(wù)控制
這篇文章主要介紹了Spring如何基于xml實(shí)現(xiàn)聲明式事務(wù)控制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
LeetCode程序員面試題之無(wú)重復(fù)字符的最長(zhǎng)子串
Java計(jì)算無(wú)重復(fù)字符的最長(zhǎng)子串是一種常見(jiàn)的字符串處理算法,它的目的是找出一個(gè)字符串中無(wú)重復(fù)字符的最長(zhǎng)子串。該算法可以很好地解決一些字符串處理問(wèn)題,比如尋找字符串中重復(fù)字符的位置,以及計(jì)算字符串中無(wú)重復(fù)字符的最長(zhǎng)子串的長(zhǎng)度。2023-02-02

