深深的码丨Java ArrayList 源码透析
ArrayList 本質是通過一個可變長的數組來存儲不定量的元素,且當元素通過不同方式被添加存儲時,總是先計算自身容量,若符合擴容標準則對數組進行擴容并拷貝原有元素
本文將基于 JDK8 對 ArrayList 源碼中的構造ArrayList()、存儲add()、刪除remove()、擴容grow()、序列化(writeObject()、readObject()) 等過程中所涉及 JDK 源碼做行級解釋
若您有遇到其它相關問題,非常歡迎在評論中留言,我和其他讀者小伙伴們將幫助解決并持續更新至此文,達到幫助更多人的目的。若感本文對您有所幫助請點個贊吧!
ArrayList 的結構定義
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {// 略··· }定義 ArrayList 時繼承 AbstractList 抽象類表示這是一個數組隊列,含有增刪改查遍歷等常用操作。實現 RandomAccess 接口表示此對象可進行隨機訪問。實現 Cloneable 接口表示此對象可被克隆。實現 Serializable 接口表示此對象可被序列化與反序列化,接下來開始進入正題!
ArrayList 實例化
ArrstList 共提供了 3 種 構造方法,支持指定長度和指定元素內容,滿足各種常見場景下對容量的需求
// 默認初始容量private static final int DEFAULT_CAPACITY = 10;// 若初始化時指定了長度或添加集合,但長度為0,則賦值為此對象。若elementData等于此對象,表示有參實例化,首次add時數組僅擴容至1// 如:ArrayList<Integer> mArrayList = new ArrayList<Integer>(0);private static final Object[] EMPTY_ELEMENTDATA = new Object[0];// 若初始化時不指定長度或添加集合則賦值為此對象。若elementData等于此對象,表示是無參實例化,首次add時數組默認擴充容量至10// 如:ArrayList<Integer> mArrayList = new ArrayList<Integer>(); // 區別賦值的原因猜測是因為后者實例方式更符合實際開發需要,所以首次就給了較多容量,減少多次擴容的資源開支private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];// elementData 是用來保存元素的數組。因為他的容量通常大于實際使用量,會產生空余空間,所以被修飾為transient ,// 表示不可被序列化,避免序列化時時間與空間的浪費,而 ArrayList 的序列化與反序列化通過writeObject和readObject完成transient Object[] elementData;// 當前數組長度private int size;// 最大數組長度private static final int MAX_ARRAY_SIZE = 2147483639;// 第1種:初始化一個長度為0的空數組public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}// 第2種:初始化時指定數組長度public ArrayList(int var1) {// 檢查長度參數 var1 是否合法,若合法則按需創建數組if (var1 > 0) {this.elementData = new Object[var1];} else {// 若 var1 非零則表示為負數,拋出非法參數異常if (var1 != 0) {throw new IllegalArgumentException("Illegal Capacity: " + var1);}// 若 var1 為零,則創建長度為0的數組this.elementData = EMPTY_ELEMENTDATA;}}// 第3種:初始化時批量添加元素public ArrayList(Collection<? extends E> var1) {// 將傳入的集合轉為數組,并將結果進行賦值this.elementData = var1.toArray();// 若轉換結果不為空,則繼續處理if ((this.size = this.elementData.length) != 0) {if (this.elementData.getClass() != Object[].class) {this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);}} else {// 若傳入的集合為空,則僅僅只是初始化一個空數組this.elementData = EMPTY_ELEMENTDATA;}}ArrayList 添加元素
ArrstList 共提供 4 種添加元素方法,支持指定位置、批量添加,在此檢查是否符合擴容條件
// 第1種:添加一個在數組創建時指定類型的元素public boolean add(E var1) {// 檢查是否需要擴容this.ensureCapacityInternal(this.size + 1);// 向數組尾部插入新元素(size表示當前數組中最后一個被插入元素的下標)this.elementData[this.size++] = var1;return true;}// 第2種:在指定位置,添加一個在數組創建時指定類型的元素public void add(int var1, E var2) {// 檢查下標邊界是否合法this.rangeCheckForAdd(var1);// 檢查是否需要擴容this.ensureCapacityInternal(this.size + 1);// 移動當前數組內元素位置System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);// 向指定位置插入新元素this.elementData[var1] = var2;// 更新尾元素下標位置++this.size;}// 第3種:添加一組指定類型的元素public boolean addAll(Collection<? extends E> var1) {// 轉化為數組Object[] var2 = var1.toArray();// 記錄數組長度int var3 = var2.length;// 檢查是否需要擴容this.ensureCapacityInternal(this.size + var3);// 向數組尾部插入一組新元素System.arraycopy(var2, 0, this.elementData, this.size, var3);// 更新尾元素下標位置this.size += var3;// 返回批量添加結果return var3 != 0;}// 第4種:指定起始位置,添加一組指定類型的元素,public boolean addAll(int var1, Collection<? extends E> var2) {// 檢測起始位置是否合法this.rangeCheckForAdd(var1);// 轉化為數組Object[] var3 = var2.toArray();// 記錄數組長度int var4 = var3.length;// 檢查是否需要擴容this.ensureCapacityInternal(this.size + var4);// 計算所需插入區間的下標int var5 = this.size - var1;if (var5 > 0) {// 移動當前元素位置System.arraycopy(this.elementData, var1, this.elementData, var1 + var4, var5);}// 將新數組插入指定位置System.arraycopy(var3, 0, this.elementData, var1, var4);// 更新尾元素下標位置this.size += var4;// 返回批量添加結果return var4 != 0;}相關方法源碼解析
// 計算數組長度private void ensureCapacityInternal(int var1) {// 若當前數組長度為 0 ,則比較 (10) 與 (目標長度) ,結果取大并更新目標數組長度值if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {var1 = Math.max(10, var1);}// 根據目標數組長度值,檢查是否需要擴容this.ensureExplicitCapacity(var1);}// 檢查是否需要擴容private void ensureExplicitCapacity(int var1) {// fail-fast 機制用到的修改次數計數(ArrayList非線程安全,所以在使用迭代器過程中被修改的話,會拋出 ConcurrentModificationExceptions)++this.modCount;// 如果計算出來的目標數組長度 大于 當前數組的長度,則表示數組需要擴容if (var1 - this.elementData.length > 0) {this.grow(var1);}}// 數組擴容private void grow(int var1) {// 當前數組長度int var2 = this.elementData.length;// 擴容數組長度(默認1.5倍,也就是增加當前長度的50%)int var3 = var2 + (var2 >> 1);// 如果擴容數組長度 小于 傳入的目標數組長度,則更新擴容目標if (var3 - var1 < 0) {var3 = var1;}// 擴容最大邊界檢查if (var3 - 2147483639 > 0) {var3 = hugeCapacity(var1);}// 數組擴容至目標長度,且保存現有元素this.elementData = Arrays.copyOf(this.elementData, var3);}// 檢查目標插入下標邊界是否合法private void rangeCheckForAdd(int var1) {if (var1 > this.size || var1 < 0) {throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));}}// 擴容長度檢查private static int hugeCapacity(int var0) {if (var0 < 0) {// 非法參數則拋出異常throw new OutOfMemoryError();} else {// 如果擴容目標大于 最大允許數組長度,則比較 是否大于 2147483639,大于的話 擴容目標=2147483647,反之擴容目標=2147483639return var0 > 2147483639 ? 2147483647 : 2147483639;}}ArrayList 刪除元素
ArrstList 共提供 3 種刪除元素方法,支持通過下標或對象刪除
// 第1種:刪除指定下標的元素(從0開始)public E remove(int var1) {// 檢測下標是否合法this.rangeCheck(var1);// fail-fast 機制用到的修改次數計數(ArrayList非線程安全,所以在使用迭代器過程中被修改的話,會拋出 ConcurrentModificationExceptions)++this.modCount;// 獲取即將被刪除的元素Object var2 = this.elementData(var1);// 計算需要移動的元素數量int var3 = this.size - var1 - 1;if (var3 > 0) {// 移動元素(被刪除的元素,將被它后一個元素直接覆蓋)System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var3);}// 將尾部元素置空,因為它已被復制到前一個下標位置this.elementData[--this.size] = null;// 返回處理結果return var2;}// 第2種:刪除指定對象public boolean remove(Object var1) {// 遍歷起始下標int var2;// 如果需要刪除空對象if (var1 == null) {// 遍歷當前數組中的空對象(但僅僅刪除首個符合要求的元素)for(var2 = 0; var2 < this.size; ++var2) {// 發現目標if (this.elementData[var2] == null) {// 刪除this.fastRemove(var2);return true;}}} else {// 遍歷數組中的目標對象(但僅僅刪除首個符合要求的元素)for(var2 = 0; var2 < this.size; ++var2) {// Object 的 equals 和 == 比較的均是地址值// String 的 == 比較的是值,equals 則先比較地址,再順序比較字符if (var1.equals(this.elementData[var2])) {// 若相等則刪除this.fastRemove(var2);return true;}}}return false;}// 第3種:清空public void clear() {++this.modCount;for(int var1 = 0; var1 < this.size; ++var1) {this.elementData[var1] = null;}this.size = 0;}相關方法源碼解析
// 檢查下標是否合法private void rangeCheck(int var1) {// 若刪除的元素下標大于數組內最后一個元素的下標,拋出 IndexOutOfBoundsException// var1 是下標,從0開始 ; this.size 是數組長度,從1開始,所以最后一個元素的下標最大也只能是 this.size - 1if (var1 >= this.size) {throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));}}// 執行 remove() 的刪除操作(也可以講是覆蓋)private void fastRemove(int var1) {// 執行了刪除操作,計數更新(用于 Fail-Fast 機制)++this.modCount;// 計算需要移動的元素數量int var2 = this.size - var1 - 1;if (var2 > 0) {// 移動元素(被刪除的元素,將被它后一個元素直接覆蓋)System.arraycopy(this.elementData, var1 + 1, this.elementData, var1, var2);}// 將尾部元素置空,因為它已被復制到前一個下標位置this.elementData[--this.size] = null;}System.arraycopy() 數組復制
在對 ArrayList 進行操作的的過程中,System.arraycopy() 這個 native 方法被頻繁使用,它的作用是實現高效的數組間復制,在此標注下各項參數含義,避免產生困擾
public static native void arraycopy(Object var0, int var1, Object var2, int var3, int var4);Object var0 :源數組
int var1 : 源數組復制起始下標
Object var2 : 目標數組
int var3 : 目標數組存儲起始下標
int var4 : 需復制長度
ArrayList 序列化
通過源碼我們可以看到這項定義: transient Object[] elementData;,這表示被 transient 修飾的數組不可被序列化,但 elementData 又是實際存儲了元素的數組,且 ArrayList 肯定可以序列化傳輸,那么這么操作的原因是為什么?
很簡單,當 Arraylist 被定以后,每次添加元素都需檢查當前容量是否符合擴容標準,而擴容并不會在數組長度被用完時進行,所以數組的長度總是大于實際使用長度,而在這種情況下直接序列化對象,必然造成時間成本與空間資源的浪費,所以 ArrayList 內提供了 writeObject 與 readObject 兩個方法,以 this.size 為循環條件,只處理有效元素
transient Object[] elementData;// 略···private void writeObject(ObjectOutputStream var1) throws IOException {int var2 = this.modCount;var1.defaultWriteObject();var1.writeInt(this.size);for(int var3 = 0; var3 < this.size; ++var3) {var1.writeObject(this.elementData[var3]);}if (this.modCount != var2) {throw new ConcurrentModificationException();}}private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {this.elementData = EMPTY_ELEMENTDATA;var1.defaultReadObject();var1.readInt();if (this.size > 0) {this.ensureCapacityInternal(this.size);Object[] var2 = this.elementData;for(int var3 = 0; var3 < this.size; ++var3) {var2[var3] = var1.readObject();}}}若您有遇到其它相關問題,非常歡迎在評論中留言,我和其他讀者小伙伴們將幫助解決并持續更新至此文,達到幫助更多人的目的。若感本文對您有所幫助請點個贊吧!
總結
以上是生活随笔為你收集整理的深深的码丨Java ArrayList 源码透析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对抗生成网络(GAN)学习笔记
- 下一篇: 目标检测算法回顾之发展概览