關(guān)于Java中ArrayList的源碼分析
ArrayList分析
private static final long serialVersionUID = 8683452581122892189L; /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;
ArrayList里常用6個(gè)屬性:
四個(gè)常量屬性:
serialVersionUID
:是用于在序列化和反序列化過(guò)程中進(jìn)行核驗(yàn)的一個(gè)版本號(hào)。
DEFAULT_CAPACITY
:默認(rèn)的初始化容量10
EMPTY_ELEMENTDATA
:空實(shí)例時(shí)的數(shù)組實(shí)例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:默認(rèn)大小的空實(shí)例時(shí)的數(shù)組實(shí)例
前兩個(gè)解釋的比較抽象
elementData
:Object數(shù)組,集合真正用來(lái)存數(shù)據(jù)的容器。
size
:集合大小
關(guān)于serialVersionUID可以參考文末補(bǔ)充內(nèi)容
ArrayList的三個(gè)構(gòu)造器(構(gòu)造方法)
public ArrayList() //空參 public ArrayList(int initialCapacity) //帶有初始化容量 public ArrayList(Collection<? extends E> c)//以一個(gè)集合來(lái)初始化
因?yàn)锳rrayList集合底層就是采用elementData[]數(shù)組來(lái)存儲(chǔ)數(shù)據(jù),在創(chuàng)建集合時(shí):
1、使用空參構(gòu)造器時(shí)。
只是將DEFAULTCAPACITY_EMPTY_ELEMENTDATA
這個(gè)空數(shù)組賦值給了elementData數(shù)組來(lái)完成集合的初始化,因?yàn)?code>DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前創(chuàng)建好的,所以在創(chuàng)建集合時(shí)的操作只是一個(gè)賦值操作,簡(jiǎn)化了創(chuàng)建數(shù)組的時(shí)間。
transient Object[] elementData; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
2、使用有參(initialCapacity)構(gòu)造方法時(shí)。不廢話,先看源碼:
public ArrayList( @Range(from = 0, to = java.lang.Integer.MAX_VALUE) 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); } }
他會(huì)首先判斷傳進(jìn)來(lái)的initialCapacity
是否是大于0的:
如果不大于0他執(zhí)行的操作和使用空參構(gòu)造器執(zhí)行的操作類似,只是這次賦值給elementData
數(shù)組的是EMPTY_ELEMENTDATA數(shù)組,此次空數(shù)組賦值竟然和上次空參不一樣,這是為什么呢?為什么要?jiǎng)?chuàng)建出兩個(gè)空數(shù)組呢?暫且按下不表。
如果大于0,他會(huì)新創(chuàng)建一個(gè)initialCapacity大小的數(shù)組賦值給elementData
。至于傳進(jìn)來(lái)的是一個(gè)小于0的數(shù),當(dāng)然結(jié)果就是死路一條。
3、構(gòu)造方法參數(shù)是集合時(shí)。
public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }
他會(huì)首先將傳進(jìn)來(lái)的集合轉(zhuǎn)化為一個(gè)Object數(shù)組,然后判斷該數(shù)組是否為空。
如果為空,他仍會(huì)執(zhí)行給elementData
數(shù)組賦值EMPTY_ELEMENTDATA
數(shù)組的操作,到這有沒(méi)有發(fā)現(xiàn)奇妙的一點(diǎn)?無(wú)論集合第一次創(chuàng)建時(shí)采用的哪一個(gè)構(gòu)造方法,如果傳進(jìn)來(lái)的是一個(gè)“空內(nèi)容”,他都會(huì)使用底層已經(jīng)創(chuàng)建好的數(shù)組來(lái)完成初始化。
如果不為空,判斷他們兩個(gè)是否是同一個(gè)集合。如果是,將a數(shù)組直接賦值給elementData
。如果不是,將a數(shù)組復(fù)制到elementData
。
問(wèn)題
為什么ArrayList源碼里要定義兩個(gè)空數(shù)組呢?直接定義一個(gè)豈不是更節(jié)省空間?
可以看一下源碼中對(duì)DEFAULTCAPACITY_EMPTY_ELEMENTDATA
的注釋:
We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
大致意思就是:是為了在第一次添加元素時(shí)判斷去給數(shù)組inflate(擴(kuò)容)多少。 這就涉及到了集合在第一次添加元素時(shí)的操作和集合的擴(kuò)容。
以下時(shí)第一次添加時(shí)的源碼。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
在第一次添加元素時(shí)會(huì)先調(diào)用ensureCapacityInternal
方法,同時(shí)將添加一個(gè)元素后的集合大小size
傳參過(guò)去,在ensureCapacityInternal
方法中調(diào)用ensureExplicitCapacity()
方法,在執(zhí)行此方法之前,會(huì)先調(diào)用calculateCapacity
方法。如果此時(shí)elementData
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
相等,也就是采用的空參構(gòu)造器的初始化的第一次添加,則返回默認(rèn)容量和當(dāng)前容量的較大者,當(dāng)然第一次添加肯定是返回默認(rèn)容量(DEFAULT_CAPACITY)。最后進(jìn)入ensureExplicitCapacity
方法,根據(jù)minCapacity - elementData.length > 0
判斷容量是否足夠,然而判斷是否要執(zhí)行擴(kuò)容方法grow
,;
走到這就會(huì)發(fā)現(xiàn),ArrayList的初始化容量不會(huì)在創(chuàng)建集合時(shí)進(jìn)行,而會(huì)在第一次添加元素時(shí)進(jìn)行。而且只有采用的是空參構(gòu)造方法時(shí),才會(huì)在第一次添加元素時(shí)將最小容量設(shè)置為minCapacity = DEFAULT_CAPACITY = 10。
這還不是最終的擴(kuò)容,最終的擴(kuò)容在grow方法中:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//讓newCapacity等于原來(lái)容量的1.5倍 if (newCapacity - minCapacity < 0) //如果newCapacity比minCapacity小 newCapacity = minCapacity; //讓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); }
建議
因?yàn)锳rrayList每擴(kuò)容一次都要將原數(shù)組數(shù)據(jù)復(fù)制到新數(shù)組,所以建議給定一個(gè)預(yù)估計(jì)的初始化容量,減少數(shù)組擴(kuò)容的次數(shù),這是ArrayList集合比較重要的優(yōu)化策略。
知識(shí)補(bǔ)充
serialVersionUID 是干什么的?
我們有時(shí)候在寫代碼的時(shí)候,對(duì)于一個(gè)需要序列化的類,如果不去寫 serialVersionUID
,編譯器可能就會(huì)提示我們 The serializable class ClassName does not declare a static final serialVersionUID field of type long
。
有使用過(guò) MyBatis-plus 框架的同學(xué)應(yīng)該也發(fā)現(xiàn),在使用反向代碼生成時(shí),所生成的實(shí)體類也都帶有 static
final
進(jìn)行修飾的 long
類型 serialVersionUID
。那么到底 serialVersionUID 是什么呢?有什么用?
附官方文檔連接:https://docs.oracle.com/javase/1.5.0/docs/api/java/io/Serializable.html
它是什么?
簡(jiǎn)單概括而言, serialVersionUID
是用于在序列化和反序列化過(guò)程中進(jìn)行核驗(yàn)的一個(gè)版本號(hào)。
序列化運(yùn)行時(shí)將一個(gè)版本號(hào)(稱為serialVersionUID
)與每個(gè)可序列化類相關(guān)聯(lián),該版本號(hào)在反序列化期間用于驗(yàn)證序列化對(duì)象的發(fā)送方和接收方是否為該對(duì)象加載了與序列化兼容的類。
如果接收方為對(duì)象加載的類與相應(yīng)發(fā)送方類的serialVersionId
不同,則反序列化將導(dǎo)致InvalidClassException
。
可序列化類可以通過(guò)聲明名為 serialVersionUID
的字段顯式聲明自己的 serialVersionUID
,且該字段必須是static
、final
的且類型為long
:
ANY-ACCESS-MODIFIER static final long serialVersionUID=42L;
不聲明會(huì)怎樣?
如Java(TM)對(duì)象序列化規(guī)范中所講述的,如果可序列化類沒(méi)有顯式聲明serialVersionUID
,則序列化運(yùn)行時(shí)將根據(jù)類的各個(gè)方面計(jì)算該類的默認(rèn)serialVersionUID
值。
但是,強(qiáng)烈建議所有可序列化類顯式聲明serialVersionUID
值,因?yàn)槟J(rèn)的 serialVersionUID
計(jì)算對(duì)類詳細(xì)信息高度敏感,這些詳細(xì)信息可能因編譯器實(shí)現(xiàn)而異,因此在反序列化過(guò)程中可能會(huì)導(dǎo)致意外的InvalidClassExceptions
。
因此,為了保證在不同的java編譯器實(shí)現(xiàn)中SerialVersionId
值是一致的,可序列化類必須聲明一個(gè)顯式的SerialVersionId
值。還強(qiáng)烈建議顯式 serialVersionUID
聲明盡可能使用 private
修飾符,因?yàn)榇祟惵暶鲀H適用于立即聲明的類——serialVersionUID
字段不可用作繼承成員。
其他問(wèn)題
Q: 如果父類被序列化,默認(rèn)情況下子類也被序列化,所以我們也需要為 child 聲明 serialVersionUID
嗎?
A: 建議對(duì)子類,或者說(shuō)每一個(gè)存在序列化需求的類都進(jìn)行 serialVersionUID
的指定,并且如上建議,采用 private
進(jìn)行修飾,避免子類對(duì)父類的 protected 繼承(我還沒(méi)碰上炸毛的情況,所以也不好講繼承后會(huì)在什么情況下出現(xiàn)什么樣的問(wèn)題)
Q: 如果我不序列化,還需要指定嗎?
A:如果不存在序列化需求,也就不存在序列化與反序列化中的比對(duì),原則上不聲明 serialVersionUID
也是可以的
到此這篇關(guān)于關(guān)于Java中ArrayList的源碼分析的文章就介紹到這了,更多相關(guān)Java ArrayList內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入解析Java編程中的StringBuffer與StringBuider
這篇文章主要介紹了Java編程中的StringBuffer與StringBuider,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09@PathVariable注解,讓spring支持參數(shù)帶值功能的案例
這篇文章主要介紹了@PathVariable注解,讓spring支持參數(shù)帶值功能的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02JAVA中的函數(shù)式接口Function和BiFunction詳解
這篇文章主要介紹了JAVA中的函數(shù)式接口Function和BiFunction詳解,JDK的函數(shù)式接口都加上了@FunctionalInterface注解進(jìn)行標(biāo)識(shí),但是無(wú)論是否加上該注解只要接口中只有一個(gè)抽象方法,都是函數(shù)式接口,需要的朋友可以參考下2024-01-01SpringCache常用注解及key中參數(shù)值為null問(wèn)題解析
這篇文章主要介紹了SpringCache常用注解及key中參數(shù)值為null的問(wèn)題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09SpringBoot如何通過(guò)配置文件(yml,properties)限制文件上傳大小
這篇文章主要介紹了SpringBoot如何通過(guò)配置文件(yml,properties)限制文件上傳大小,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03關(guān)于maven工程多模塊、項(xiàng)目打包問(wèn)題
這篇文章主要介紹了關(guān)于maven工程多模塊、項(xiàng)目打包問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03JDK的一個(gè)Bug監(jiān)聽(tīng)文件變更的初步實(shí)現(xiàn)思路
這篇文章主要介紹了JDK的一個(gè)Bug監(jiān)聽(tīng)文件變更要小心了,本篇文章就帶大家簡(jiǎn)單實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的功能,并分析一下對(duì)應(yīng)的Bug和優(yōu)缺點(diǎn),需要的朋友可以參考下2022-05-05通過(guò)代理類實(shí)現(xiàn)java連接數(shù)據(jù)庫(kù)(使用dao層操作數(shù)據(jù))實(shí)例分享
java通過(guò)代理類實(shí)現(xiàn)數(shù)據(jù)庫(kù)DAO操作代碼分享,大家參考使用吧2013-12-12