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

