關(guān)于ArrayList的動態(tài)擴(kuò)容機(jī)制解讀
1. 前言
對于 ArrayList 的動態(tài)擴(kuò)容機(jī)制想必大家都聽說過,之前的文章中也談到過,不過由于時(shí)間久遠(yuǎn),早已忘卻。
所以利用這篇文章做做筆記,加深理解記憶
2. ArrayList 的動態(tài)擴(kuò)容機(jī)制
要了解其動態(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í),如果被包含的列表中沒有數(shù)據(jù),那么elementData指向該數(shù)組 private static final Object[] EMPTY_ELEMENTDATA = {}; // 如果使用默認(rèn)構(gòu)造方法,那么elementData指向該數(shù)組 // 在添加元素時(shí)會判斷是否是使用默認(rèn)構(gòu)造器第一次添加,如果是數(shù)組就會擴(kuò)容至10個(gè)容量 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默認(rèn)未初始化的儲存 ArrayList集合元素的底層數(shù)組,其長度就是 ArrayList的容量 transient Object[] elementData; // 私有的elementData數(shù)組中具體的元素對象的數(shù)量,可通過size方法獲得。默認(rèn)初始值為0,在add、remove等方法時(shí)size會改變 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 的動態(tài)擴(kuò)容
根據(jù)上面的兩個(gè)結(jié)論,不管是使用默認(rèn)或有參構(gòu)造器時(shí),我們可以使其初始容量為 0,那么它的動態(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ù)組長度,它是隨著數(shù)組擴(kuò)容而動態(tài)變化的
- 如果是第一次 add 元素時(shí),它為 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; // 擴(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ù)組長度,它是隨著數(shù)組拷貝而動態(tài)變化的
- 如果是第一次 add 元素時(shí),它為 0
- 之后它是隨著 oldCapacity + (oldCapacity >> 1) 而動態(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)無參構(gòu)造器時(shí),它的初始容量就是 0
如果使用 ArrayList 的有參構(gòu)造器時(shí),它的初始容量就是你傳入的參數(shù) initialCapacity的值
3.2. 動態(tài)擴(kuò)容大小
ArrayList 的初始容量為 0,當(dāng)?shù)谝淮?add 元素時(shí),才會發(fā)生擴(kuò)容操作,它的擴(kuò)容大小是 10
當(dāng)?shù)谝淮?add 元素完成后,此時(shí)的 elementData.length = 10,后面要想發(fā)生擴(kuò)容操作,必須 minCapacity - elementData.length > 0 為 true,即 minCapacity 最小為 11,也就是說當(dāng) ArrayList 第十一次 add 時(shí),才會接著發(fā)生擴(kuò)容操作,且擴(kuò)容大小為 15 = 10 + 5
3.3. 動態(tài)擴(kuò)容大小測試
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)文章
java多線程編程之InheritableThreadLocal
這篇文章主要為大家詳細(xì)介紹了java多線程編程之InheritableThreadLocal,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10spring security自定義認(rèn)證登錄的全過程記錄
這篇文章主要給大家介紹了關(guān)于spring security自定義認(rèn)證登錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12Spring Boot中Elasticsearch的連接配置原理與使用詳解
在Spring Boot中,我們可以通過Elasticsearch實(shí)現(xiàn)對數(shù)據(jù)的搜索和分析,本文將介紹Spring Boot中Elasticsearch的連接配置、原理和使用方法,感興趣的可以了解一下2023-09-09java ArrayBlockingQueue阻塞隊(duì)列的實(shí)現(xiàn)示例
ArrayBlockingQueue是一個(gè)基于數(shù)組實(shí)現(xiàn)的阻塞隊(duì)列,本文就來介紹一下java ArrayBlockingQueue阻塞隊(duì)列的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter
這篇文章主要介紹了dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03idea項(xiàng)目中target文件提示拒絕訪問的解決
這篇文章主要介紹了idea項(xiàng)目中target文件提示拒絕訪問的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11