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