一起看源碼:深入List 分支 ArrayList


簡介: ArrayList 他的底層數據結構是以數組的方式進行組織數據的,且通過一定的算 法邏輯動態擴容數組的長度,或可理解 ArrayList 底層是一個動態數組。與 Java 中的原生的數組相比,它的容量是能動態增長的。

繼承結構

在這裏插入圖片描述
AbstractCollection:實現了 Collection 中大量的函數,除了特定的幾個函數 iterator()和 size()之外 的函數
List:有序隊列接口,提供了一些通過下標訪問元素的函數 List 是有序的隊列,List 中的每一個元素都有一個索引;第一個元素的索引值是 0,往後的元素的索引值依次+1
RandomAccess:RandmoAccess 接口,即提供了隨機訪問功能。RandmoAccess 是 java 中用 來被 List 實現,爲 List 提供快速訪問功能的。在 ArrayList 中,我們即可以通過元素的序號快速獲取元素對象;這就是快速隨機訪問
AbstractList:該接口繼承於 AbstractCollection,並且實現 List 接口的抽象類。 它實現了 List 中除 size()、get(int location)之外的函數。 AbstractList 的主要作用:它實現了 List 接口中的大部分函數和 AbstractCollection 相比,AbstractList 抽象類中,實現了 iterator()接口

ArrayList

ArrayList 包含了兩個重要的對象:elementData 和 size。
在這裏插入圖片描述
elementData: 是Object[]類型的數組,它保存了添加到 ArrayList 中的元素。如果通過不含參數的構造函數 ArrayList()來創建 ArrayList,則 elementData 的容量默認是 10
size :則是動態數組的實際大小。

(動態)數組的數據結構特點總結:
1.內存需要連續的空間保證
2.順序添加操作涉及到動態擴容問題
3.除尾部位置元素的添加,刪除操作都涉及到位置移動操作
4.隨機查找效率快(下標搜尋)

ArrayList 源碼設計者的優雅之道

上邊說到elementData 的容量默認是 10。實際上,ArrayList 的無參構造並沒有完成對 elementData 數組對象的構造。

在這裏插入圖片描述
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什麼呢?只是一個空的數組,所以說無參構造並沒有完成對 elementData 數組對象的構造
在這裏插入圖片描述
那麼elementData是什麼時候纔會完成構造的呢?注意看對elementData的這句描述:譯爲:將在添加第一個元素時擴展爲DEFAULT_CAPACITY。
在這裏插入圖片描述
DEFAULT_CAPACITY的定義:
在這裏插入圖片描述
那意思就是在第一次add的時候纔會構造一個默認長度爲10 的數組。
那咱們就去add方法中看一看到底是不是這個樣子的(大家可以打開源碼跟着步伐一起走,溫馨提示:idea中alt+7打開本類構造):

1.找到add()方法:
裏面調用了ensureCapacityInternal(int minCapacity)方法。注意傳入的是size+1,上邊說了size就是動態數組的長度,那麼第一次進來現在size肯定是0
在這裏插入圖片描述
2.進入ensureCapacityInternal(int minCapacity)方法:
這裏又調了ensureExplicitCapacity(int minCapacity)方法,傳入的是一個calculateCapacity(Object[] elementData, int minCapacity)方法的返回值,那麼下一步

在這裏插入圖片描述
3.進入calculateCapacity(Object[] elementData, int minCapacity)看看返回的是什麼
注意:這裏傳進來的兩個參數elementData和minCapacity
elementData:上邊說了,就是存放元素的數組嘛
minCapacity:就是再上一步中add方法傳過來的size嘛,這裏minCapacity就是0
看if判斷的條件:elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA肯定是true嘛,不知道爲啥的往上翻到ArrayList 的無參構造
進入if判斷:return Math.max(DEFAULT_CAPACITY, minCapacity);返回DEFAULT_CAPACITY和minCapacity這兩個數中的最大值
DEFAULT_CAPACITY:爲10(往上翻)
minCapacity:爲0
就是返回了10唄。
在這裏插入圖片描述
4.return到ensureCapacityInternal(int minCapacity)方法,之後進入ensureExplicitCapacity(int minCapacity)方法中傳入10
modCount++;是什麼呢?後邊會說到的。這裏簡單說下就是用於FailFast機制檢測。這裏下不管,繼續往下看
if判斷條件:minCapacity - elementData.length > 0爲true吧,minCapacity爲10,elementData現在還爲空所以length爲0
進入if語句:調grow(int minCapacity)方法,傳入10
在這裏插入圖片描述
5.進入grow(int minCapacity)方法
在這裏插入圖片描述
自上往下解釋:
oldCapacity = elementData.length即爲0
newCapacity = oldCapacity + (oldCapacity >> 1);就是oldCapacity+oldCapacity 右移一位即oldCapacity+oldCapacity /2。即newCapacity = 0+0/2即爲0
第一個if判斷:肯定爲true嘛,0-10<0沒異議。newCapacity = minCapacity
即newCapacity重新賦值爲10了。
第二個if判斷條件newCapacity - MAX_ARRAY_SIZE > 0。MAX_ARRAY_SIZE是什麼呢?看下圖ArrayList類中定義爲Integer.MAX_VALUE - 8;啥意思?就是二十多億減8啊。
在這裏插入圖片描述那麼10-20多億>0肯定不滿足的。所以直接跳過此條if語句,看下邊
elementData = Arrays.copyOf(elementData, newCapacity);就是拷貝數組重新賦給elementData。

那麼elementData就是在這裏被初始化爲長度10的數組的。

看jdk官方api文檔中:
在這裏插入圖片描述

總結:我們可以通過源碼分析和斷點跟蹤的方式.發現最終,他的初始化是在真正的第 一次添加元素時纔會完成數據對象的構建.跟我們的截圖中的註釋解釋是一模一 樣的,那這樣做的優雅之處在哪裏呢?
ArrayList 對象 new 的過程並不會實際的創建 10 長度的 Object[]用於對象的 存儲,如果後續並沒有使用到 Arraylist 對象進行操作的話,這部分的內存就是浪 費的.只有當程序第一次使用 ArrayList 添加元素時,纔會完成通過 grow 函數完 成對對象數組的創建.主要的意義是防止內存的浪費。

集合迭代器獲取元素優雅之道

 	List list = new ArrayList();
    //常見的方式
     Iterator iterator = list.iterator();
     while (iterator.hasNext()){
         Object obj = iterator.next();
     }
     //優雅的方式
     for (Iterator tempiterator = list.iterator();tempiterator.hasNext();){
         Object obj = tempiterator.next();
     }

上訴的優雅之處在於 tempIterator 對象存在於 for 所在的作用域範圍之內.在 for
範圍結束之後隨時可以完成對該對象的垃圾回收

FailFast 機制

簡介:快速失敗機制,是 java 集合類應對併發訪問在對集合進行迭代過程中,內部對象結構發生變化 一種防護措施。
這種錯誤檢測的機制爲這種有可能發生錯誤 , 通過拋出java.util.ConcurrentModificationException
簡單點說:就是你在遍歷一個集合的時候對它進行添加或刪除的操作時就會拋這個異常,就是一種防護措施,不讓你在遍歷集合的時候做修改操作。
例如下面一段代碼,就會拋出ConcurrentModificationException

public static void main(String[] args) {
        List list = new ArrayList();
        list.add("aaa");
        for (Iterator tempiterator = list.iterator();tempiterator.hasNext();){
            list.add("bbb");
            Object obj = tempiterator.next();
        }
    }

那麼它是如何做到 FailFast 檢測的呢?
1.這裏就拿ArrayList類來看,ArrayList中有一個叫Itr的內部類實現了Iterator接口。找到這個類,看到
int expectedModCount = modCount;modCount是不是上文中ArrayList做添加操作的時候看到過:modCount++這個操作,這裏就是把modCount的值賦值給expectedModCount 。
在這裏插入圖片描述
2.找到類中的迭代方法next();
進來第一步就看到調用了checkForComodification()方法,看名字就知道這是一個檢驗的方法
在這裏插入圖片描述
3.進入checkForComodification()方法
這裏只是做了判斷,如果modCount != expectedModCount就會拋出ConcurrentModificationException異常。
因爲在創建迭代器的時候,modCount的值就賦值給expectedModCount了,按道理來講它們兩個應該是相等的對吧。但是我們要知道,在對集合做添加和刪除時都會對modCount進行++的操作所以說,如果我們在迭代的時候對集合進行了增刪的操作就會使modCount != expectedModCount觸發FailFast機制,導致拋出異常。

在這裏插入圖片描述
總結:modCount就相當於一個版本號。你在每次對集合進行增刪的操作時都會對這個版本號進行升一級的操作。你在迭代的時候檢驗當前版本號和創建迭代器時的版本號不一致時就會報錯。

ArrayList 中動態擴容是如何實現的

假設現在集合中有10個容量已經滿了,現在來添加第11個元素

其實上邊add方法已經見到了。咱們一起在梳理一遍:
1.找到add方法
size:當前集合的大小也就是10。
調ensureCapacityInternal(int minCapacity)方法傳入size+1也就是11
在這裏插入圖片描述
2.進入ensureCapacityInternal(int minCapacity)方法
minCapacity的值爲11
下一步直接進入ensureExplicitCapacity(int minCapacity)方法了;calculateCapacity方法詳見上邊的,就是返回了數組長度和minCapacity兩數的最大值,當前數組的長度爲10,也就是返回11嘛
在這裏插入圖片描述
3.調用ensureExplicitCapacity(int minCapacity)方法傳入11
modCount++:上邊已經說過了吧,用於FailFast 機制
if條件肯定滿足吧:11-10>0沒毛病
在這裏插入圖片描述
4.進入grow(int minCapacity)方法,傳入11
在這裏插入圖片描述
注意看:oldCapacity值爲10。newCapacity的值爲10+10/2=15吧。(右移一位就是除以2)
第一個if條件不滿足吧,第二個也不滿足吧。直接數組拷貝,重新賦值給elementData,現在elementData數組是不是就擴容到15了,就可以添加第11個元素了。

發佈了21 篇原創文章 · 獲贊 34 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章