JDK源碼閱讀(三):ArraryList源碼解析

今天來看一下ArrayList的源碼java

  • 目錄
    • 介紹
    • 繼承結構
    • 屬性
    • 構造方法
    • add方法
    • remove方法
    • 修改方法
    • 獲取元素
    • size()方法
    • isEmpty方法
    • clear方法
    • 循環數組

1.介紹

通常來說文章開始應該先介紹一下說下簡介。這裏就不介紹了 若是你不知道ArrayList是什麼的話就不必在看了。大體講一下一些經常使用的方法數組

2.繼承結構

ArrayList源碼定義:安全

ArrayList繼承結構以下:多線程

  • Serializable 序列化接口併發

  • Cloneable 前面咱們在看Object源碼中有提到這個類,主要表示能夠進行克隆dom

  • List 主要定義了一些方法實現ide

  • RandomAccess 也是一個標記類,實現RandomAccess表示該類支持快速隨機訪問。ui

3.屬性

ArrayList中的主要屬性方法有:this

初始化容量,默認爲10spa

private static final int DEFAULT_CAPACITY = 10;
複製代碼

空數組

private static final Object[] EMPTY_ELEMENTDATA = {};
複製代碼

也是一個空數組,和上面的數組相比主要的做用是在添加元素的時候知道數組膨脹了多少。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
複製代碼

ArrayList的大小

private int size;
複製代碼

注意ArrrayList中有一個modCount成員變量,來記錄修改次數,主要是在使用迭代器遍歷的時候,用來檢查列表中的元素是否發生結構性變化(列表元素數量發生改變)了,主要在多線程環境下須要使用,防止一個線程正在迭代遍歷,另外一個線程修改了這個列表的結構。好好認識下這個異常:ConcurrentModificationException。對了,ArrayList是非線程安全的。

4.構造方法

咱們來看一下ArrayList的構造方法 無參構造方法:

能夠看出無參構造會直接建立一個指定DEFAULTCAPACITY_EMPTY_ELEMENTDATA的數組,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA數組默認是空的。

因此在執行一下代碼的時候ArrayList默認建立的是一個初始化容量爲0 的數組。

ArrayList list = new ArrayList();
複製代碼

有參構造方法:

傳入建立數組的大小,若是大於0 就建立一個傳入參數大小的數組,若是等於0 就就指定爲空數組。若是小於0就會拋異常。

看源碼咱們能夠看到傳入Collection的構造方法作的事情就是複製數組,將已有的集合複製到新的集合中。

5.add方法

ArrayList底層是用數組來實現的,那咱們就一塊兒來看如下Add方法是如何實現的

如下是Add方法的實現源碼。

能夠看到ArrayList在添加元素以前先檢查一下集合的大小

在 ensureExplicitCapacity 方法中,首先對修改次數modCount加一,這裏的modCount給ArrayList的迭代器使用的,在併發操做被修改時,提供快速失敗行爲(保證modCount在迭代期間不變,不然拋出ConcurrentModificationException異常,能夠查看源碼865行),接着判斷minCapacity是否大於當前ArrayList內部數組長度,大於的話調用grow方法對內部數組elementData擴容,grow方法代碼以下:

上面的代碼能夠看出ArrayList的擴容。首先得到老數組的容量,而後 oldCapacity + (oldCapacity >> 1);計算出老數組大小的1.5倍,判斷 新容量小於參數指定容量,修改新容量,若是新容量大於最大容量的話就指定容量。

6.remove方法

ArrayList的刪除操做一共有兩種一種是根據索引刪除,一種是根據內容刪除。

根據索引刪除元素

remove方法表示刪除索引index處的元素,首先經過 rangeCheck 方法判斷給定索引的範圍,超過集合大小則拋出異常;接着經過 System.arraycopy 方法對數組進行自身拷貝。 根據元素刪除

咱們能夠看到首先判斷一下是否爲空。不爲空的話就開始循環查找元素,用equals來判斷元素是否相同,若是一致就調用fastRemove來刪除元素。而後經過System.arraycopy進行自身複製。

7.修改方法

 經過調用 set(int index, E element) 方法在指定索引 index 處的元素替換爲 element。並返回原數組的元素。先經過rangCheck來檢查索引的合法性,若是不合法(負數,或者其餘值)會拋出異常。

8.獲取元素

由於自己ArrayList就是用數組來實現的,因此獲取元素就相對的來講簡單一點。

首先也是調用rangeCheck方法來判斷索引是否合法,而後在直接根據索引回去元素

根據元素查找索引

直接返回第一次出現的元素。不然返回-1

9.size()方法

直接返回的size大小。

9.isEmpty方法

直接返回size==0的結果,是否是很是簡單。

10.clear方法

循環把元素賦值爲空,便於GC回收

11.循環數組

for循環

for循環可能在java中是最經常使用的遍歷方法主要實現:

由於咱們前面說過get方法能夠經過索引來獲取元素。同理。

迭代器 iterator

先看實現:

咱們來看一下源碼怎麼實現的:

返回一個 Itr 對象,這個類是 屬於ArrayList 的內部類。

/** * An optimized version of AbstractList.Itr */
    private class Itr implements Iterator<E> {
        //遊標, 下一個要返回的元素的索引
        int cursor; 
        // 返回最後一個元素的索引; 若是沒有這樣的話返回-1.
        int lastRet = -1; 
        int expectedModCount = modCount;

        Itr() {}
        //經過 cursor != size 判斷是否還有下一個元素
        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        //迭代器進行元素迭代時同時進行增長和刪除操做,會拋出異常
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
                //遊標向後移動一位
            cursor = i + 1;
            //返回索引爲i處的元素,並將 lastRet賦值爲i
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
            //調用ArrayList的remove方法刪除元素
                ArrayList.this.remove(lastRet);
                //遊標指向刪除元素的位置,原本是lastRet+1的,這裏刪除一個元素,而後遊標就不變了
                cursor = lastRet;
                //lastRet恢復默認值-1
                lastRet = -1;
                //expectedModCount值和modCount同步,由於進行add和remove操做,modCount會加1
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
        //前面在新增元素add() 和 刪除元素 remove() 時,咱們能夠看到 modCount++。修改set() 是沒有的
        final void checkForComodification() {
            if (modCount != expectedModCount)
            //也就是說不能在迭代器進行元素迭代時進行增長和刪除操做,不然拋出異常
                throw new ConcurrentModificationException();
        }
    }

複製代碼

從上面的代碼咱們得出,在遍歷的時候若是刪除或者新增元素都會拋異常出來,而修改不會。看下方例子。

拋出異常:

小弟不才,若有錯誤請指出。喜歡請關注,慢慢更新JDK源碼閱讀筆記

相關文章
相關標籤/搜索