關(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:是用于在序列化和反序列化過程中進行核驗的一個版本號。
DEFAULT_CAPACITY:默認的初始化容量10
EMPTY_ELEMENTDATA:空實例時的數(shù)組實例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默認大小的空實例時的數(shù)組實例
前兩個解釋的比較抽象
elementData:Object數(shù)組,集合真正用來存數(shù)據(jù)的容器。
size:集合大小
關(guān)于serialVersionUID可以參考文末補充內(nèi)容
ArrayList的三個構(gòu)造器(構(gòu)造方法)
public ArrayList() //空參 public ArrayList(int initialCapacity) //帶有初始化容量 public ArrayList(Collection<? extends E> c)//以一個集合來初始化
因為ArrayList集合底層就是采用elementData[]數(shù)組來存儲數(shù)據(jù),在創(chuàng)建集合時:
1、使用空參構(gòu)造器時。
只是將DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個空數(shù)組賦值給了elementData數(shù)組來完成集合的初始化,因為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);
}
}他會首先判斷傳進來的initialCapacity是否是大于0的:
如果不大于0他執(zhí)行的操作和使用空參構(gòu)造器執(zhí)行的操作類似,只是這次賦值給elementData數(shù)組的是EMPTY_ELEMENTDATA數(shù)組,此次空數(shù)組賦值竟然和上次空參不一樣,這是為什么呢?為什么要創(chuàng)建出兩個空數(shù)組呢?暫且按下不表。
如果大于0,他會新創(chuàng)建一個initialCapacity大小的數(shù)組賦值給elementData。至于傳進來的是一個小于0的數(shù),當然結(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;
}
}他會首先將傳進來的集合轉(zhuǎn)化為一個Object數(shù)組,然后判斷該數(shù)組是否為空。
如果為空,他仍會執(zhí)行給elementData數(shù)組賦值EMPTY_ELEMENTDATA數(shù)組的操作,到這有沒有發(fā)現(xiàn)奇妙的一點?無論集合第一次創(chuàng)建時采用的哪一個構(gòu)造方法,如果傳進來的是一個“空內(nèi)容”,他都會使用底層已經(jīng)創(chuàng)建好的數(shù)組來完成初始化。
如果不為空,判斷他們兩個是否是同一個集合。如果是,將a數(shù)組直接賦值給elementData。如果不是,將a數(shù)組復制到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(擴容)多少。 這就涉及到了集合在第一次添加元素時的操作和集合的擴容。
以下時第一次添加時的源碼。
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)造器的初始化的第一次添加,則返回默認容量和當前容量的較大者,當然第一次添加肯定是返回默認容量(DEFAULT_CAPACITY)。最后進入ensureExplicitCapacity方法,根據(jù)minCapacity - elementData.length > 0 判斷容量是否足夠,然而判斷是否要執(zhí)行擴容方法grow,;
走到這就會發(fā)現(xiàn),ArrayList的初始化容量不會在創(chuàng)建集合時進行,而會在第一次添加元素時進行。而且只有采用的是空參構(gòu)造方法時,才會在第一次添加元素時將最小容量設(shè)置為minCapacity = DEFAULT_CAPACITY = 10。
這還不是最終的擴容,最終的擴容在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);
}建議
因為ArrayList每擴容一次都要將原數(shù)組數(shù)據(jù)復制到新數(shù)組,所以建議給定一個預估計的初始化容量,減少數(shù)組擴容的次數(shù),這是ArrayList集合比較重要的優(yōu)化策略。
知識補充
serialVersionUID 是干什么的?
我們有時候在寫代碼的時候,對于一個需要序列化的類,如果不去寫 serialVersionUID,編譯器可能就會提示我們 The serializable class ClassName does not declare a static final serialVersionUID field of type long。
有使用過 MyBatis-plus 框架的同學應(yīng)該也發(fā)現(xiàn),在使用反向代碼生成時,所生成的實體類也都帶有 static final 進行修飾的 long 類型 serialVersionUID 。那么到底 serialVersionUID 是什么呢?有什么用?
附官方文檔連接:https://docs.oracle.com/javase/1.5.0/docs/api/java/io/Serializable.html
它是什么?
簡單概括而言, serialVersionUID 是用于在序列化和反序列化過程中進行核驗的一個版本號。
序列化運行時將一個版本號(稱為serialVersionUID)與每個可序列化類相關(guān)聯(lián),該版本號在反序列化期間用于驗證序列化對象的發(fā)送方和接收方是否為該對象加載了與序列化兼容的類。
如果接收方為對象加載的類與相應(yīng)發(fā)送方類的serialVersionId不同,則反序列化將導致InvalidClassException。
可序列化類可以通過聲明名為 serialVersionUID 的字段顯式聲明自己的 serialVersionUID,且該字段必須是static、final的且類型為long:
ANY-ACCESS-MODIFIER static final long serialVersionUID=42L;
不聲明會怎樣?
如Java(TM)對象序列化規(guī)范中所講述的,如果可序列化類沒有顯式聲明serialVersionUID,則序列化運行時將根據(jù)類的各個方面計算該類的默認serialVersionUID值。
但是,強烈建議所有可序列化類顯式聲明serialVersionUID值,因為默認的 serialVersionUID 計算對類詳細信息高度敏感,這些詳細信息可能因編譯器實現(xiàn)而異,因此在反序列化過程中可能會導致意外的InvalidClassExceptions。
因此,為了保證在不同的java編譯器實現(xiàn)中SerialVersionId值是一致的,可序列化類必須聲明一個顯式的SerialVersionId值。還強烈建議顯式 serialVersionUID 聲明盡可能使用 private 修飾符,因為此類聲明僅適用于立即聲明的類——serialVersionUID字段不可用作繼承成員。
其他問題
Q: 如果父類被序列化,默認情況下子類也被序列化,所以我們也需要為 child 聲明 serialVersionUID 嗎?
A: 建議對子類,或者說每一個存在序列化需求的類都進行 serialVersionUID 的指定,并且如上建議,采用 private 進行修飾,避免子類對父類的 protected 繼承(我還沒碰上炸毛的情況,所以也不好講繼承后會在什么情況下出現(xiàn)什么樣的問題)
Q: 如果我不序列化,還需要指定嗎?
A:如果不存在序列化需求,也就不存在序列化與反序列化中的比對,原則上不聲明 serialVersionUID 也是可以的
到此這篇關(guān)于關(guān)于Java中ArrayList的源碼分析的文章就介紹到這了,更多相關(guān)Java ArrayList內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot配置多數(shù)據(jù)源并集成Druid和mybatis的操作
這篇文章主要介紹了springboot配置多數(shù)據(jù)源并集成Druid和mybatis的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Java 常用類解析:java異常機制,異常棧,異常處理方式,異常鏈,異常丟失詳解
這篇文章主要介紹了Java 常用類解析:java異常機制,異常棧,異常處理方式,異常鏈,異常丟失詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03
SpringBoot常用注解,thymeleaf,數(shù)據(jù)提交的實現(xiàn)
SpringBoot簡化了微服務(wù)配置,提供快速啟動和內(nèi)嵌容器化web項目,常用注解包括@Component、@RestController等,Thymeleaf為前端頁面渲染提供支持,數(shù)據(jù)提交時需使用@RequestBody注解2026-01-01
Spring Boot 把配置文件和日志文件放到j(luò)ar外部
如果不想使用默認的application.properties,而想將屬性文件放到j(luò)ar包外面,怎么做呢?下面小編給大家?guī)砹藘煞N方法解決Spring Boot 把配置文件和日志文件放到j(luò)ar外部問題,感興趣的朋友一起看看吧2018-02-02
有關(guān)Java常見的誤解小結(jié)(來看一看)
下面小編就為大家?guī)硪黄嘘P(guān)Java常見的誤解小結(jié)(來看一看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05

