淺談Java中ArrayList的擴容機制
相信大家對 ArrayList 這個類都不陌生吧,ArrayList 底層是基于數(shù)組實現(xiàn)的,作為可變長度的數(shù)組用于存儲任意類型的對象,那么是否有了解過 ArrayList 是如何實現(xiàn)可變長的呢,它的擴容機制是什么呢?
這篇文章將從源碼的角度來講講 ArrayList 的擴容機制,有興趣的讀者們可以接著往下了解。
ArrayList 的構(gòu)造函數(shù)
在了解 ArrayList 的擴容機制之前,讓我們先看看它的構(gòu)造函數(shù)有哪些?
ArrayList 的字段
// 默認(rèn)初始容量大小 private static final int DEFAULT_CAPACITY = 10; // 空數(shù)組 private static final Object[] EMPTY_ELEMENTDATA = {}; // 默認(rèn)空數(shù)組 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 存儲數(shù)據(jù)的數(shù)組 transient Object[] elementData; // 元素個數(shù) private int size; // 最大數(shù)組長度 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
相信大家對這里的 EMPTY_ELEMENTDATA
和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
為何需要有兩個空數(shù)組感到疑惑,這兩個空數(shù)組之間的區(qū)別在什么地方呢?對于這個問題等看完后面的源碼后就會有答案了。
ArrayList 的構(gòu)造函數(shù)
// 有參構(gòu)造函數(shù)(initialCapacity:指定的初始化集合大?。? public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // 創(chuàng)建長度為initialCapacity的數(shù)組 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 使用空數(shù)組(長度為0) this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } // 無參構(gòu)造器 public ArrayList() { // 使用默認(rèn)空數(shù)組(一開始長度為0,等集合被使用后初始化為10) this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } // 有參構(gòu)造器(根據(jù)指定的集合構(gòu)造列表) public ArrayList(Collection<? extends E> c) { // 通過toArray()方法轉(zhuǎn)換為數(shù)組 elementData = c.toArray(); if ((size = elementData.length) != 0) { // 數(shù)組長度不為0且數(shù)組不是Object類型數(shù)據(jù)則更換類型為Object的新數(shù)組 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 數(shù)組長度為0則使用空數(shù)組 this.elementData = EMPTY_ELEMENTDATA; } }
通過上面源碼中的三種 ArrayList 的構(gòu)造函數(shù)可以看到根據(jù)不同的參數(shù)構(gòu)造列表,同時根據(jù)不同的參數(shù)導(dǎo)致的列表的數(shù)組 elementData
被賦予的值也是不同的。
到這里應(yīng)該可以看出 EMPTY_ELEMENTDATA
和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的區(qū)別:
EMPTY_ELEMENTDATA
:當(dāng)構(gòu)造函數(shù)使用指定initialCapacity
為 0 或指定集合且集合元素為 0 時,會使得elementData
被賦值為EMPTY_ELEMENTDATA
。這里的空數(shù)組表示初始化后的數(shù)組長度就是 0 。DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:當(dāng)構(gòu)造函數(shù)使用無參構(gòu)造函數(shù)時,elementData
被賦值為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。這里的空數(shù)組表示開始長度為 0,當(dāng)列表被使用時,初始化后的數(shù)組長度為 10(DEFAULT_CAPACITY
),也就是默認(rèn)數(shù)組大小。(通過后面的add()
源碼能夠更好理解)
到這里就可以理解為什么 ArrayList 有兩個空數(shù)組字段,主要是為了設(shè)置默認(rèn)的數(shù)組長度,也就是為了區(qū)分當(dāng)前數(shù)組是默認(rèn)數(shù)組(也就是還不確定大小,先設(shè)置為空數(shù)組,等使用后在設(shè)置為默認(rèn)長度),還是已經(jīng)確認(rèn)長度為 0 的空數(shù)組。
ArrayList 的擴容機制
ArrayList 的擴容機制核心方法是 grow()
方法,這里從 add()
方法進入詳細看看 ArrayList 的擴容過程。
擴容過程源碼分析
添加元素方法:add()
public boolean add(E e) { // size + 1 確保添加后長度足夠,進入方法判斷是否需要擴容 ensureCapacityInternal(size + 1); // 添加元素 elementData[size++] = e; return true; }
判斷是否是默認(rèn)長度方法:ensureCapacityInternal()
private void ensureCapacityInternal(int minCapacity) { // 數(shù)組為默認(rèn)數(shù)組時,minCapacity只會在10之上。 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } // 進入方法判斷是否需要擴容 ensureExplicitCapacity(minCapacity); }
判斷是否需要擴容方法:ensureExplicitCapacity()
private void ensureExplicitCapacity(int minCapacity) { // 用于記錄修改次數(shù)的計數(shù)器,保證多線程下拋出ConcurrentModificationException異常 modCount++; // minCapacity最小需求容量小于當(dāng)前數(shù)組長度時則需要擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); }
擴容方法:grow()
private void grow(int minCapacity) { // 舊容量等于數(shù)組長度 int oldCapacity = elementData.length; // 新容量等于數(shù)組長度 + 0.5 * 數(shù)組長度 也就是 1.5 數(shù)組長度 int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量還是小于最小需求容量時,直接將新容量賦值為最小需求容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 新容量大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)調(diào)用hugeCapacity()擴容到Integer.MAX_VALUE if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 數(shù)組拷貝,將數(shù)據(jù)遷移到新數(shù)組 elementData = Arrays.copyOf(elementData, newCapacity); }
擴容過程梳理
下面以使用無參構(gòu)造函數(shù)的列表為例,分別講講第1個元素插入和第11個元素插入的擴容過程:
添加第1個元素的過程:
- 調(diào)用
add(e)
方法添加元素,此時的size=0
。 - 調(diào)用
ensureCapacityInternal(1)
方法判斷是否是默認(rèn)長度數(shù)組,此時該方法的傳參是size + 1
代表目前列表需要的最小容量。進入該方法后,因為使用的是無參構(gòu)造器,故這里的數(shù)組是默認(rèn)長度數(shù)組,所以minCapacity
最小容量被置為 10,也就是列表默認(rèn)長度DEFAULT_CAPACITY
。 - 調(diào)用
ensureExplicitCapacity(10)
方法判斷是否需要擴容,此時由于minCapacity=10
大于 數(shù)組長度elementData.length=0
所以需要擴容。 - 調(diào)用
grow(10)
方法進行擴容,此時舊數(shù)組容量為oldCapacity=0
,新數(shù)組容量為newCapacity=0
,而最小容量minCapacity=10
,因為新數(shù)組容量小于最小容量,故將新數(shù)組容量重新賦值為newCapacity=minCapacity=10
,然后進行數(shù)組拷貝,到此完成擴容操作。
添加第11個元素的過程:
- 調(diào)用
add(e10)
方法添加元素,此時的size=10
。 - 調(diào)用
ensureCapacityInternal(11)
方法判斷是否是默認(rèn)長度數(shù)組,此時該方法的傳參是size + 1
代表目前列表需要的最小容量。進入該方法后,由于已經(jīng)超出了默認(rèn)長度 10,所以這里minCapacity
設(shè)置為 11。 - 調(diào)用
ensureExplicitCapacity(11)
方法判斷是否需要擴容,此時由于minCapacity=11
大于 數(shù)組長度elementData.length=10
所以需要擴容。 - 調(diào)用
grow(11)
方法進行擴容,此時舊數(shù)組容量為oldCapacity=10
,新數(shù)組容量為newCapacity=15
,而最小容量minCapacity=11
,因為新數(shù)組容量大于最小容量,故將新數(shù)組容量依舊為15,然后進行數(shù)組拷貝,到此完成擴容操作,新數(shù)組的長度擴容到了 15,并將舊數(shù)組的元素遷移到新數(shù)組中。
以上就是擴容的全過程,擴容公式為 newCapacity=oldCapacity+(oldCapacity>>1)newCapacity = oldCapacity + (oldCapacity >> 1)newCapacity=oldCapacity+(oldCapacity>>1),也就是新容量取值為舊容量的 1.5 倍。
補充:數(shù)組拷貝 System.arraycopy() 方法
擴容后的數(shù)據(jù)遷移調(diào)用的是 Arrays.copyOf()
方法底層調(diào)用的是 System.arraycopy()
,這里簡單介紹一下這個常用的方法。
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
該方法的參數(shù)如下:
src
:源數(shù)組srcPos
:源數(shù)組拷貝的起始位置dest
:目標(biāo)數(shù)組destPos
:目標(biāo)數(shù)組拷貝的起始位置length
:需要拷貝的數(shù)組元素的數(shù)量
這里作為補充知識簡單了解一下即可。
總要有總結(jié)
以上就是 ArrayList 的擴容機制的內(nèi)容了,主要總結(jié)如下:
- ArrayList 是基于動態(tài)數(shù)組實現(xiàn)的,可以擴容或縮短(縮短可以手動調(diào)用
trimToSize()
方法,ArrayList在元素小于一半長度時會自動縮短長度,這里不作過多說明)數(shù)組的大小從而實現(xiàn)可變長的數(shù)組。 - ArrayList 中聲明了
EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
兩個字段,雖然都是空數(shù)組,但是兩者之間有著不同的作用。 - ArrayList 的擴容機制采用的是新數(shù)組容量擴容為舊數(shù)組容量的 1.5 倍。
補充: ArrayList 提供了 ensureCapacity(int minCapacity)
用于實現(xiàn)手動擴容到指定的容量,這樣在需要添加大量數(shù)據(jù)時可以不必重復(fù)進行擴容操作,提高性能。
到這里就是本篇的所有內(nèi)容了,ArrayList 類中還有許多有意思的方法,有興趣的讀者們可以自行去查看它們的源碼。
到此這篇關(guān)于淺談Java中ArrayList的擴容機制的文章就介紹到這了,更多相關(guān)Java中 ArrayList擴容內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
knife4j+springboot3.4異常無法正確展示文檔
本文主要介紹了knife4j+springboot3.4異常無法正確展示文檔,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01mybatis mapper.xml獲取insert后的自增ID問題
這篇文章主要介紹了mybatis mapper.xml獲取insert后的自增ID問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05springcloud安裝rabbitmq并配置延遲隊列插件的過程詳解
本期主要講解如何利用docker快速安裝rabbitmq并且配置延遲隊列插件,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05spring boot springMVC擴展配置實現(xiàn)解析
這篇文章主要介紹了spring boot springMVC擴展配置實現(xiàn)解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08