天天看點

ArrayList源碼分析(JDK1.8)二 、源碼分析

一、簡介

 ArrayList結構

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
           

1、AbstractList,實作了List, RandomAccess, Cloneable, java.io.Serializable這些接口。

2、ArrayList 繼承了AbstractList,實作了List。它是一個數組隊列,提供了相關的添加、删除、修改、周遊等功能。

3、ArrayList 實作了RandmoAccess接口,即提供了随機通路功能。RandmoAccess是java中用來被List實作,為List提供快速通路功能的。在ArrayList中,我們即可以通過元素的序号快速擷取元素對象;這就是快速随機通路。

4、ArrayList 實作了Cloneable接口,即覆寫了函數clone(),能被克隆。

5、ArrayList 實作java.io.Serializable接口,這意味着ArrayList支援序列化,能通過序列化去傳輸。

注意

和Vector不同,ArrayList中的操作不是線程安全的!是以,建議在單線程中才使用ArrayList,而在多線程中可以選擇Vector或者CopyOnWriteArrayList,或者使用Collections工具類的synchronizedList方法将其包裝

ArrayList源碼分析(JDK1.8)二 、源碼分析
ArrayList源碼分析(JDK1.8)二 、源碼分析

二 、源碼分析

1、ArrayList屬性

//序列号
    private static final long serialVersionUID = 8683452581122892189L;

    //預設初始容量
    private static final int DEFAULT_CAPACITY = 10;

    //一個空數組,當使用者指定ArrayList容量為0時,傳回該數組
    private static final Object[] EMPTY_ELEMENTDATA = {};

      /**
     * 一個空數組執行個體
     * - 當使用者沒有指定 ArrayList 的容量時(即調用無參構造函數),傳回的是該數組==>剛建立一個 ArrayList 時,其内資料量為 0。
     * - 當使用者第一次添加元素時,該數組将會擴容,變成預設容量為 10(DEFAULT_CAPACITY) 的一個數組===>通過  ensureCapacityInternal() 實作
     * 它與 EMPTY_ELEMENTDATA 的差別就是:該數組是預設傳回的,而後者是在使用者指定容量為 0 時傳回
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //目前資料對象存放地方,目前對象不參與序列化
    //這個關鍵字最主要的作用就是當序列化時,被transient修飾的内容将不會被序列 化
    transient Object[] elementData; // non-private to simplify nested class access

    //ArrayList實際存儲的資料數量
    private int size;

    //繼承于AbstractList
    //集合數組修改次數的辨別
    protected transient int modCount = 0;

           

size和elementData.length是不相同的

  • size:是指目前集合中存在的元素個數
  • elementData.length:是指目前集合指定的容量大小

例如:

如果我們new ArrayList()時,ArrayList隻是給我們預設的elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此時隻是空數組,

隻有第一次add時才預設數組的大小為DEFAULT_CAPACITY=10 ,此時elementData.length=10,而size=0

2、ArrayList構造函數

/**
     * 建立一個初試容量的、空的ArrayList
     * @param  initialCapacity  初始容量
     * @throws IllegalArgumentException 當初試容量值非法(小于0)時抛出
     */
    public ArrayList(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);
        }
    }

    /**
     * 無參構造函數:
     * - 建立一個 空的 ArrayList,此時其内數組緩沖區 elementData = {}, 長度為 0
     * - 當元素第一次被加入時,擴容至預設容量 10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 建立一個包含collection的ArrayList
     * @param c 要放入 ArrayList 中的集合,其内元素将會全部添加到建立的 ArrayList 執行個體中
     * @throws NullPointerException 當參數 c 為 null 時抛出異常
     */
    public ArrayList(Collection<? extends E> c) {
        //把集合傳化成Object[]數組
        elementData = c.toArray();
        //轉化後的數組長度賦給目前ArrayList的size,并判斷是否為0
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //c.toArray可能不會傳回 Object[],可以檢視 java 官方編号為 6260652 的 bug
            if (elementData.getClass() != Object[].class)
                // 若 c.toArray() 傳回的數組類型不是 Object[],則利用 Arrays.copyOf(); 來構造一個大小為 size 的 Object[] 數組
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 替換空數組
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
           

Arrays.copyOf方法

/*
複制指定的數組,截斷或填充空字元,以便複制具有指定的長度
- original:要複制的數組 
- newLength:要傳回的數組的長度 
*/
public static char[] copyOf(char[] original, int newLength) {
    char[] copy = new char[newLength];
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

//指定複制數組的範圍
public static char[] copyOfRange(char[] original, int from, int to) {
    int newLength = to - from;
    if (newLength < 0)
        throw new IllegalArgumentException(from + " > " + to);
    char[] copy = new char[newLength];
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}
/*
original - 要複制的數組 
newLength - 要傳回的副本的長度 
newType - 要傳回的副本的類型
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        //根據class的類型來決定是new 還是反射去構造一個泛型數組
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        //利用native函數,批量指派元素至新數組中。
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
           

System.arraycopy方法

/*
将指定源數組的數組,從指定位置,複制到指定數組的指定位置,要指派的數組元素數量等于length。
- src:源數組
- srcPos:源數組中的起始位置
- dest:目标數組
- destPos:目标數組的起始位置
- length:要指派的數組元素數量
*/
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
           

3、方法分析

1、add(E e)方法

快速掌握的方法:debug一遍

1.1 如果沒有指定初始化容量,第一次調用add()方法,會初始化容量為10

如果一開始指定了初始化容量,假如為4,那麼隻有在添加第5個元素時,ensureExplicitCapacity方法裡的if語句裡的minCapacity-elementData.length > 0 才能成立,此時就會以原容量的1.5倍進行擴容。

ArrayList源碼分析(JDK1.8)二 、源碼分析

1.2 如果超出數組容量,預設配置設定大小為原數組的1.5倍。如果配置設定的數組過大(超過Integer.MAX_VALUE - 8,一般都不會那麼大),則調用hugeCapacity靜态方法 ,如果minCapacity介于Integer.MAX_VALUE - 8到Integer.MAX_VALUE,則直接配置設定一個Integer.MAX_VALUE大小的數組,否則抛出OutOfMemoryError。

1、確定數組已使用長度(size)加1之後足夠存下 下一個資料

2、修改次數modCount 辨別自增1,如果目前數組已使用長度(size)加1後的大于目前的數組長度,則調用grow方法,增長數組,grow方法會将目前數組的長度變為原來容量的1.5倍。

3、確定新增的資料有地方存儲之後,則将新元素添加到位于size的位置上。

4、傳回添加成功布爾值。

/**
     *增加指定的元素到ArrayList的最後位置
     * @param e 要添加的元素
     * @return
     */
    public boolean add(E e) {
        // 确定ArrayList的容量大小---嚴謹
        // 注意:size + 1,保證資源空間不被浪費,
        // ☆☆☆按目前情況,保證要存多少個元素,就隻配置設定多少空間資源
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
/**
     * 私有方法:明确 ArrayList 的容量,提供給本類使用的方法
     * - 用于内部優化,保證空間資源不被浪費:尤其在 add() 方法添加時起效
     * @param minCapacity    指定的最小容量
     */
    private void ensureCapacityInternal(int minCapacity) {
        // 若 elementData == {},則取 minCapacity 為 預設容量和參數 minCapacity 之間的最大值
        // 注:ensureCapacity() 是提供給使用者使用的方法,在 ArrayList 的實作中并沒有使用
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity= Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    /**
     * 私有方法:明确 ArrayList 的容量
     * - 用于内部優化,保證空間資源不被浪費:尤其在 add() 方法添加時起效
     * @param minCapacity    指定的最小容量
     */
    private void ensureExplicitCapacity(int minCapacity) {
        // 将“修改統計數”+1,該變量主要是用來實作fail-fast機制的
        modCount++;
        // 防止溢出代碼:確定指定的最小容量 > 數組緩沖區目前的長度
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * 數組緩沖區最大存儲容量
     * - 一些 VM 會在一個數組中存儲某些資料--->為什麼要減去 8 的原因
     * - 嘗試配置設定這個最大存儲容量,可能會導緻 OutOfMemoryError(當該值 > VM 的限制時)
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 私有方法:擴容,以確定 ArrayList 至少能存儲 minCapacity 個元素
     * - 擴容計算:newCapacity = oldCapacity + (oldCapacity >> 1);  擴充目前容量的1.5倍
     * @param minCapacity    指定的最小容量
     */
    private void grow(int minCapacity) {
        // 防止溢出代碼
        int oldCapacity = elementData.length;
        // 運算符 >> 是帶符号右移. 如 oldCapacity = 10,則 newCapacity = 10 + (10 >> 1) = 10 + 5 = 15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)  // 若 newCapacity 依舊小于 minCapacity
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)   // 若 newCapacity 大于最大存儲容量,則進行大容量配置設定
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    /**
     * 私有方法:大容量配置設定,最大配置設定 Integer.MAX_VALUE
     * @param minCapacity
     */
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }
           

>> 算術右移

 void add(int index, E element)

1、檢查數組下标是否越界,越界抛出異常

2、確定數組已使用長度(size)加1之後足夠存下 下一個資料

3、修改次數modCount 辨別自增1,如果目前數組已使用長度(size)加1後的大于目前的數組長度,則調用grow方法,增長數組,grow方法會将目前數組的長度變為原來容量的1.5倍。

4、使用System.arraycopy把index後面的元素都往後移動一位,騰出index位置

5、将新的資料放到指定位置index上

public void add(int index, E element) {
        //判斷索引是否越界
        rangeCheckForAdd(index);
        //確定已使用數組長度size+1能夠存下資料
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //elementData:要複制的數組,index從哪裡開始複制
        //elementData:目标數組,index複制到的數組第幾個開始,最後一個是複制長度
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    //檢查下标是否越界
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

   
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }
           

2、remove(int index)方法

1、判斷索引是否越界

2、增加修改的次數

3、儲存要删除的元素為oldValue

4、将指定位置index+1往後的元素都向前移動一位,覆寫需要删除的元素

5、将最後一個元素置空,GC

6、傳回删除的元素

/**
     * 移除指定位置的元素
     * index 之後的所有元素依次左移一位
     * @param index 指定位置
     * @return 被移除的元素
     * @throws IndexOutOfBoundsException
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;//要移動的長度
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        // 将最後一個元素置空
        elementData[--size] = null;

        return oldValue;
    }

      /**
     * 檢查數組是否在界線内
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
           

 remove(Object o)

/**
     * 移除list中指定的第一個元素(符合條件索引最低的)
     * 如果list中不包含這個元素,這個list不會改變
     * 如果包含這個元素,index 之後的所有元素依次左移一位
     * @param o 這個list中要被移除的元素
     * @return
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /**
     * 快速删除第 index 個元素
     * 和public E remove(int index)相比
     * 私有方法,跳過檢查,不傳回被删除的值
     * @param index 要删除的腳标
     */
    private void fastRemove(int index) {
        modCount++;//這個地方改變了modCount的值了
        int numMoved = size - index - 1;//移動的個數
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        elementData[--size] = null; //将最後一個元素清除
    }
           

 3、iterator疊代器

疊代器統一了對容器的通路方式,能夠将周遊序列的操作與序列底層的結構分離。

1、使用方法iterator()要求容器傳回一個Iterator。Iterator将準備号傳回序列的第一個元素

2、使用next()獲得序列中的下一個元素

3、使用hasNext()檢查序列是否還有元素

4、使用remove()将疊代器新近傳回的元素删除

/**
     * 以一種合适的排序傳回一個iterator到元素的結尾
     */
    public Iterator<E> iterator() {
        return new Itr();
    }


    private class Itr implements Iterator<E> {
        int cursor;       // 下一個元素傳回的索引
        int lastRet = -1; // 最後一個元素傳回的索引  -1 if no such
        int expectedModCount = modCount;

        /**
         * 是否有下一個元素
         */
        public boolean hasNext() {
            return cursor != size;
        }

        /**
         * 傳回list中的值
         */
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;//i目前元素的索引
            if (i >= size)//第一次檢查:角标是否越界越界
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)//第二次檢查,list集合中數量是否發生變化
                throw new ConcurrentModificationException();
            cursor = i + 1; //cursor 下一個元素的索引
            return (E) elementData[lastRet = i];//最後一個元素傳回的索引
        }

        /**
         * 移除集合中的元素
         */
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //移除list中的元素
                ArrayList.this.remove(lastRet);
                //由于cursor比lastRet大1,所有這行代碼是指指針往回移動一位
                cursor = lastRet;
                //将最後一個元素傳回的索引重置為-1
                lastRet = -1;
                //重新設定了expectedModCount的值,避免了ConcurrentModificationException的産生
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        /**
         * 檢查modCount是否等于expectedModCount
         * 在 疊代時list集合的元素數量發生變化時會造成這兩個值不相等
         */
        final void checkForComodification() {
            //當expectedModCount和modCount不相等時,就抛出ConcurrentModificationException
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
           

并發修改異常

深入分析for-each和疊代器

1、foreach可以操作數組:   底層依然采用for循環+ 索引來擷取數組元素.

2、foreach可以操作Iterable的執行個體:底層其實采用的Iterator(疊代器).

ArrayList源碼分析(JDK1.8)二 、源碼分析

為什麼會報ConcurrentModificationException異常?

1、Iterator 是工作在一個獨立的線程中,并且擁有一個 mutex 鎖。

2、 Iterator 被建立之後會建立一個指向原來對象的單鍊索引表,當原來的對象數量發生變化時,

這個索引表的内容不會同步改變,是以當索引指針往後移動的時候就找不到要疊代的對象。

3、是以按照 fail-fast 原則 Iterator 會馬上抛出 java.util.ConcurrentModificationException 異常。

4、是以 Iterator 在工作的時候是不允許被疊代的對象被改變的。但你可以使用 Iterator 本身的方法 remove() 來删除對象,

 5、Iterator.remove() 方法會在删除目前疊代對象的同時維護索引的一緻性。

在Collection接口中存在删除指定元素的方法:boolean  remove(Object ele),該方法隻能從集合中删除元素,不能把疊代器中指定的元素也删除。

如何解決并發修改異常呢?

不要使用集合對象的删除方法,使用Iterator中的remove方法,方法會從兩個線程中同時移除被删除的元素,.保證了兩個線程的同步.

并發修改問題

ListItr,可以雙向疊代

/**
     * AbstractList.ListItr 的優化版本
     * ListIterator 與普通的 Iterator 的差別:
     * - 它可以進行雙向移動,而普通的疊代器隻能單向移動
     * - 它可以添加元素(有 add() 方法),而後者不行
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        /**
         * 是否有前一個元素
         */
        public boolean hasPrevious() {
            return cursor != 0;
        }

        /**
         * 擷取下一個元素的索引
         */
        public int nextIndex() {
            return cursor;
        }

        /**
         * 擷取 cursor 前一個元素的索引
         * - 是 cursor 前一個,而不是目前元素前一個的索引。
         * - 若調用 next() 後馬上調用該方法,則傳回的是目前元素的索引。
         * - 若調用 next() 後想擷取目前元素前一個元素的索引,需要連續調用兩次該方法。
         */
        public int previousIndex() {
            return cursor - 1;
        }

        /**
         * 傳回 cursor 前一進制素
         */
        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)//第一次檢查:索引是否越界
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)//第二次檢查
                throw new ConcurrentModificationException();
            cursor = i;//cursor回移
            return (E) elementData[lastRet = i];//傳回 cursor 前一進制素
        }

        /**
         * 将數組的最後一個元素,設定成元素e
         */
        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //将數組最後一個元素,設定成元素e
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        /**
         * 添加元素
         */
        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;//目前元素的索引後移一位
                ArrayList.this.add(i, e);//在i位置上添加元素e
                cursor = i + 1;//cursor後移一位
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
           

4 、其他部分方法

/**
     * 将數組緩沖區大小調整到實際 ArrayList 存儲元素的大小,即 elementData = Arrays.copyOf(elementData, size);
     * - 該方法由使用者手動調用,以減少空間資源浪費的目的 ce.
     */
    public void trimToSize() {
        // modCount 是 AbstractList 的屬性值:protected transient int modCount = 0;
        // [問] modCount 有什麼用?
        modCount++;
        // 當實際大小 < 數組緩沖區大小時
        // 如調用預設構造函數後,剛添加一個元素,此時 elementData.length = 10,而 size = 1
        // 通過這一步,可以使得空間得到有效利用,而不會出現資源浪費的情況
        if (size < elementData.length) {
            // 注意這裡:這裡的執行順序不是 (elementData = (size == 0) ) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
            // 而是:elementData = ((size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size));
            // 這裡是運算符優先級的文法
            // 調整數組緩沖區 elementData,變為實際存儲大小 Arrays.copyOf(elementData, size)
            //先判斷size是否為0,如果為0:實際存儲為EMPTY_ELEMENTDATA,如果有資料就是Arrays.copyOf(elementData, size)
            elementData = (size == 0)
                    ? EMPTY_ELEMENTDATA
                    : Arrays.copyOf(elementData, size);
        }
    }

    /**
     * 指定 ArrayList 的容量
     * @param   minCapacity   指定的最小容量
     */
    public void ensureCapacity(int minCapacity) {
        // 最小擴充容量,預設是 10
        //這句就是:判斷是不是空的ArrayList,如果是的最小擴充容量10,否則最小擴充量為0
        //上面無參構造函數建立後,當元素第一次被加入時,擴容至預設容量 10,就是靠這句代碼
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                ? 0
                : DEFAULT_CAPACITY;
        // 若使用者指定的最小容量 > 最小擴充容量,則以使用者指定的為準,否則還是 10
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    /**
     * 移除list中的所有元素,這個list表将在調用之後置空
     * - 它會将數組緩沖區是以元素置為 null
     * - 清空後,我們直接列印 list,卻隻會看見一個 [], 而不是 [null, null, ….] ==> toString() 和 疊代器進行了處理
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
           

參考:https://www.cnblogs.com/gxl1995/p/7534171344218b3784f1beb90d621337.html(合并詳細)

參考:https://blog.csdn.net/fighterandknight/article/details/61240861(分開詳細)

繼續閱讀