Java中的ArrayList容量及擴(kuò)容方式
查看JDK1.8 ArrayList的源代碼
1、默認(rèn)初始容量為10
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;
2、最大容量為 Integer.MAX_VALUE - 8
/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
原因:之前參考別人的,有待求證:
數(shù)組對(duì)象有一個(gè)額外的元數(shù)據(jù),用于表示數(shù)組的大??;
數(shù)組長(zhǎng)度size為int類型,共32位,有一位符號(hào)位,所以最大長(zhǎng)度為Integer.MAX_VALUE=2^31= 2,147,483,648;
8bytes用來(lái)存儲(chǔ)size;
3、擴(kuò)容方式:
(1)首先傳遞進(jìn)來(lái)一個(gè)希望的最小容量minCapacity;
(2)新容量newCapacity = oldCapacity + (oldCapacity >> 1),即新容量等于原容量的1.5倍;
(3)如果minCapacity > newCapacity ,newCapacity = minCapacity ;
(4)如果 newCapacity > 最大容量 MAX_ARRAY_SIZE ,newCapacity = hugeCapacity(minCapacity);
(5)以新容量拷貝原數(shù)據(jù)
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; 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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
Java ArrayList() 擴(kuò)容原理
平常都是直接使用 ArrayList(),今天特地看一下 ArrayList() 的擴(kuò)容原理。
先看下 ArrayList 的屬性以及構(gòu)造方法,這個(gè)比較重要
先看下屬性
// ArrayList 默認(rèn)容量大小 private static final int DEFAULT_CAPACITY = 10; // 一個(gè)共享的空數(shù)組, 在空實(shí)例時(shí)使用 private static final Object[] EMPTY_ELEMENTDATA = {}; // 一個(gè)共享的空數(shù)組, 在使用默認(rèn) size 的空實(shí)例時(shí)使用 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /* 存儲(chǔ) ArrayList 元素的數(shù)組緩沖區(qū) 重點(diǎn)1: ArrayList 的容量是數(shù)組緩沖區(qū)的長(zhǎng)度 重點(diǎn)2: 從這個(gè)元素也可以看的出來(lái) ArrayList() 的底層就是一個(gè) Object[] add 第一個(gè)元素時(shí), 任何帶有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都將被擴(kuò)展為 DEFAULT_CAPACITY */ transient Object[] elementData; // ArrayList 的大小, 我們平常使用的 list.size() 底層就是記錄的這個(gè) size private int size; // ArrayList 最大 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
再看構(gòu)造器,帶參構(gòu)造器
/* 帶參構(gòu)造器, 參數(shù)為容量大小, 例如: 初始化一個(gè)容器為 20 類型為 Integer 的 ArrayList ArrayList<Integer> list = new ArrayList<>(20); */ public ArrayList(int initialCapacity) { /* 初始化容量 > 0, elementData 初始化為初始化容量大小的數(shù)組 初始化容量 = 0, elementData = EMPTY_ELEMENTDATA (空數(shù)組) 初始化容量 < 0, 直接拋出異常 */ if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
參數(shù)為 Collection 的構(gòu)造器
/* 將一個(gè)參數(shù)為 Collection 的集合轉(zhuǎn)換為 ArrayList */ public ArrayList(Collection<? extends E> c) { // Collection 轉(zhuǎn)換為數(shù)組 Object[] 類型 elementData = c.toArray(); // 判斷當(dāng)前對(duì)象大小是否和 Collection 長(zhǎng)度相等并且不等于 0, false 的話 elementData 等于空數(shù)組了 if ((size = elementData.length) != 0) { // c.toArray() 可能不會(huì)正確地返回一個(gè) Object[] 數(shù)組,所以使用 Arrays.copyOf() if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
不帶參構(gòu)造器
/* 不帶參構(gòu)造器就像我們平時(shí)使用一樣, 直接 new 一個(gè) ArrayList 不需要傳遞任何參數(shù) 構(gòu)造方法中直接將 elementData 初始化為 DEFAULTCAPACITY_EMPTY_ELEMENTDATA (空數(shù)組) */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
看到這里就發(fā)現(xiàn),帶參的構(gòu)造器我們可以直接傳遞參數(shù),而默認(rèn)的構(gòu)造器怎么怎么樣初始化容量大小的呢?
add() 方法可以直接得到答案。
public boolean add(E e) { // 這一行是關(guān)鍵, 看下面 ensureCapacityInternal(size + 1); // 將元素追加到集合的末尾 假如當(dāng)前 size = 10 size++ 追加到第 11 位 elementData[size++] = e; return true; }
ensureCapacityInternal() 方法調(diào)用
private void ensureCapacityInternal(int minCapacity) { /* calculateCapacity() 方法, 剛初始化時(shí)會(huì)返回 10, 其他情況返回當(dāng)前 size + 1 ensureExplicitCapacity() 方法, 調(diào)用擴(kuò)容 */ ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { /* 使用無(wú)參構(gòu)造器創(chuàng)建創(chuàng)建 ArrayList 的集合, 此時(shí)一定是相等的 */ if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { /* 兩數(shù)相比返回最大值, 此時(shí) Math.max(10, 1); 默認(rèn)容量, 由此而來(lái) */ return Math.max(DEFAULT_CAPACITY, minCapacity); } // 不相等的話只有返回當(dāng)前的 size + 1 return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { // 增量, 記錄修改/更新次數(shù) modCount++; // 初始化: 10 - 0 > 0 // 其他: size + 1 > 0 if (minCapacity - elementData.length > 0) // 擴(kuò)容操作 grow(minCapacity); } private void grow(int minCapacity) { // 老的長(zhǎng)度, 初始化時(shí)為 0, int oldCapacity = elementData.length; // 新的長(zhǎng)度此時(shí) 0 + (0 >> 1), newCapacity = 0 int newCapacity = oldCapacity + (oldCapacity >> 1); // 初始化場(chǎng)景: 0 - 10 < 0 ? true newCapacity = 10 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 初始化場(chǎng)景: 10 - 2147483639 > 0 返回 false if (newCapacity - MAX_ARRAY_SIZE > 0) // 超大長(zhǎng)度才可以執(zhí)行這個(gè)方法, 必須大于 MAX_ARRAY_SIZE 一般不會(huì) newCapacity = hugeCapacity(minCapacity); // 復(fù)制原數(shù)組中的元素, 并擴(kuò)容 elementData = Arrays.copyOf(elementData, newCapacity); }
上看說(shuō)的是初始化場(chǎng)景,下面看一下其他場(chǎng)景,也是相當(dāng)簡(jiǎn)單
private void ensureCapacityInternal(int minCapacity) { /* calculateCapacity() 方法, 正常擴(kuò)容返回 size + 1, 比如 10 + 1, 因?yàn)槟J(rèn)長(zhǎng)度為 10 當(dāng)再次新增數(shù)據(jù)時(shí)就會(huì)出發(fā)擴(kuò)容 ensureExplicitCapacity() 方法, 調(diào)用擴(kuò)容 */ ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } /* elementData 不等于空數(shù)組 返回當(dāng)前的 size + 1, 即 10 + 1 返回 11 */ return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { // 增量, 記錄修改/更新次數(shù) modCount++; // 其他: 11 - 10 > 0 true, 觸發(fā)擴(kuò)容, 如果當(dāng)前下表是 5 的話 5 + 1 =6, 6 < 10 是, 此時(shí)不會(huì)出發(fā)擴(kuò)容 if (minCapacity - elementData.length > 0) // 擴(kuò)容操作 grow(minCapacity); } private void grow(int minCapacity) { // 老的長(zhǎng)度 10 int oldCapacity = elementData.length; // 新的長(zhǎng)度此時(shí) 10 + (10 >> 1), newCapacity = 15 int newCapacity = oldCapacity + (oldCapacity >> 1); // 15 - 11 < 0 ? false if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 15 - 2147483639 > 0 返回 false if (newCapacity - MAX_ARRAY_SIZE > 0) // 超大長(zhǎng)度才可以執(zhí)行這個(gè)方法, 必須大于 MAX_ARRAY_SIZE 一般不會(huì) newCapacity = hugeCapacity(minCapacity); // 復(fù)制原數(shù)組中的元素, 并擴(kuò)容 newCapacity = 15 elementData = Arrays.copyOf(elementData, newCapacity); }
結(jié)論
1、 觸發(fā)擴(kuò)容的關(guān)鍵是
當(dāng)前 size + 1 是否大于當(dāng)前容量,如果大于容量則證明,集合不夠用了,需要擴(kuò)容。如果小與當(dāng)前容量則證明集合還有容量不需要擴(kuò)容!
2、 每次擴(kuò)容的大小是
oldCapacity + (oldCapacity >> 1) 即: 10 + (10 >> 1)
即:當(dāng)前容量的 1.5 倍!
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- 關(guān)于Java的ArrayList數(shù)組自動(dòng)擴(kuò)容機(jī)制
- Java ArrayList擴(kuò)容機(jī)制原理深入分析
- Java基礎(chǔ)之ArrayList的擴(kuò)容機(jī)制
- 在java中ArrayList集合底層的擴(kuò)容原理
- Java使用數(shù)組實(shí)現(xiàn)ArrayList的動(dòng)態(tài)擴(kuò)容的方法
- 對(duì)Java ArrayList的自動(dòng)擴(kuò)容機(jī)制示例講解
- Java ArrayList擴(kuò)容問(wèn)題實(shí)例詳解
- Java中Arraylist動(dòng)態(tài)擴(kuò)容方法詳解
- 淺談Java中ArrayList的擴(kuò)容機(jī)制
相關(guān)文章
idea中springboot整合mybatis找不到mapper接口的原因分析
這篇文章主要介紹了idea中springboot整合mybatis找不到mapper接口的原因分析及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01關(guān)于maven install報(bào)錯(cuò)原因揭秘:parent.relativePath指向錯(cuò)誤的本地POM文件
在使用Maven進(jìn)行項(xiàng)目構(gòu)建時(shí),如果遇到'parent.relativePath'指向錯(cuò)誤的本地POM文件的問(wèn)題,可能會(huì)導(dǎo)致構(gòu)建失敗,這通常是由于父項(xiàng)目POM文件的相對(duì)路徑設(shè)置錯(cuò)誤、本地POM文件與父項(xiàng)目POM文件版本或內(nèi)容不一致所致,解決方法包括檢查并修正父項(xiàng)目POM文件中的相對(duì)路徑設(shè)置2024-09-09關(guān)于Java中配置ElasticSearch集群環(huán)境賬號(hào)密碼的問(wèn)題
這篇文章主要介紹了Java中配置ElasticSearch集群環(huán)境賬號(hào)密碼的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04Java代理模式與動(dòng)態(tài)代理之間的關(guān)系以及概念
代理模式是開(kāi)發(fā)中常見(jiàn)的一種設(shè)計(jì)模式,使用代理模式可以很好的對(duì)程序進(jìn)行橫向擴(kuò)展。動(dòng)態(tài)代理:代理類在程序運(yùn)行時(shí)被創(chuàng)建的代理方式。關(guān)鍵在于動(dòng)態(tài),程序具有了動(dòng)態(tài)特性,可以在運(yùn)行期間根據(jù)不同的目標(biāo)對(duì)象生成動(dòng)態(tài)代理對(duì)象2023-02-02spring常用注解開(kāi)發(fā)一個(gè)RESTful接口示例
這篇文章主要為大家介紹了使用spring常用注解開(kāi)發(fā)一個(gè)RESTful接口示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03spring boot + mybatis如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)的讀寫分離
這篇文章主要給大家介紹了關(guān)于spring boot + mybatis如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)的讀寫分離的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09CountDownLatch和Atomic原子操作類源碼解析
這篇文章主要為大家介紹了CountDownLatch和Atomic原子操作類的源碼解析以及理解應(yīng)用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03Spring中的FactoryBean實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Spring中的FactoryBean實(shí)現(xiàn)原理詳解,spring中有兩種類型的Bean,一種是普通的JavaBean,另一種就是工廠Bean(FactoryBean),這兩種Bean都受Spring的IoC容器管理,但它們之間卻有一些區(qū)別,需要的朋友可以參考下2024-02-02java開(kāi)發(fā)之SQL語(yǔ)句中DATE_FORMAT函數(shù)舉例詳解
要將日期值格式化為特定格式,請(qǐng)使用DATE_FORMAT函數(shù),下面這篇文章主要給大家介紹了關(guān)于java開(kāi)發(fā)之SQL語(yǔ)句中DATE_FORMAT函數(shù)的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05