ArrayList、CopyOnWriteArrayList源碼解析(JDK1.8)

本篇文章主要是學習後的知識記錄,存在不足,或許不夠深入,還請諒解。

ArrayList源碼解析

ArrayList中的變量

在這裏插入圖片描述
通過上圖可以看到,ArrayList中總共有7個變量,下面看下每個變量的作用:

    /**
     * 序列化
     */
    private static final long serialVersionUID = 8683452581122892189L;
    /**
     * 默認的初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 用於在構造函數中,初始化一個空的數組
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 用於無參構造函數中,給一個空的數組
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * 這個是真正存儲元素的數組
     */
    transient Object[] elementData; // non-private to simplify nested class access
    /**
     * size,是用來記錄arrayList中,即elementData裏的元素的大小,數組中共有多少個元素
     * @serial
     */
    private int size;
    /**
     * 這個參數,是數組所允許的最大長度,要是超出了,可能會報OutOfMemoryError異常
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArrayList構造函數

ArrayList中有三個構造函數:
在這裏插入圖片描述

    /**
     * 指定一個具有初始容量的空數組,可以自己指定容量的大小
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException 指定的初始容量值若是負值,會拋出這個異常
     */
    public ArrayList(int initialCapacity) {
        //容量大於0,就把elementData初始化一個具有initialCapacity大小的數組
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        //要是指定的容量值是0,就初始化一個空數組
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        //指定的容量值小於0.拋出不合法參數異常
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * 無參構造函數,會指定一個初始容量爲10的數組
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 構造一個包含指定元素的列表集合,按照集合返回它們的順序
     *
     * @param c 傳入的集合,會把集合中的元素放入數組中
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        //數組中元素的長度不爲0,就把c的元素copy到elementData中
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //要是元素個數是0個,就初始化一個空數組
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList中的add方法

    /**
     * add方法,會把一個元素添加到數組的末尾
     *
     * @param e 傳入的元素
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        //size是目前數組存的元素的個數,傳入size+1,即需要的數組長度
        //需要的數組長度,是可以再去容納一個元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
 //這裏傳入的是size+1
 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }
//這個方法是判斷數組是否是一個空數組,要是使用無參構造函數初始化的arrayList,那麼返回值就是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //要是elementData是一個空的數組
        //判斷需要的數組長度和默認容量值哪個大,返回最大的那個
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //要是elementData數組中有元素,直接返回minCapacity
        return minCapacity;
}
//這裏是,判斷數組是否需要擴容
private void ensureExplicitCapacity(int minCapacity) {
        //記錄數組被修改的次數
        modCount++;

        //要是需要的數組長度大於目前數組的長度,就需要擴容了(即數組的長度是否可以存入下一個元素)
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}
 /**
     * 擴容方法
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        //舊的數組的長度
        int oldCapacity = elementData.length;
        //需要擴容的數組的長度,即10*1.5=15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //要是擴容後的數組的長度還是小於需要的最小容量,那麼就把需要的最小容量給newCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //要是擴容後的數組長度比最大的數組容量還大,就需要控制了
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //把擴容之後的數組copy到長度爲newCapacity的數組中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        //參數小於0.拋異常
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //判斷需要的數組容量和數組最大容量哪個大,
        //需要的數組容量比數組最大容量還大,就返回int的最大值
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

圖式:
在這裏插入圖片描述

ArrayList中的add(插入指定位置)方法

先看下 System.arraycopy:

    // src 源數組
    // srcPos 源數組要複製的起始位置
    // dest 要賦值到的目標數組
    // destPos 目標數組放置的起始位置
    // length 複製的長度
   System.arraycopy(Object src,  int  srcPos, Object dest, int destPos,int length);

繼續add方法:

    /**
     * 將元素插入數組中指定的位置
     * @param index 指定的索引值
     * @param element 需要插入的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //參數校驗,判斷要插入的下標是否越界
        rangeCheckForAdd(index);
        //這個和add(E e)是一樣的,判斷數組是否需要擴容等
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //把插入index位置的原有元素以及該元素後面的元素,向右移動
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //把元素插入到index的位置
        elementData[index] = element;
        //數組元素的個數加1
        size++;
    }

    /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        //要是插入的位置比數組中元素的個數大,或者插入的位置值小於0,就拋出下標越界異常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * Constructs an IndexOutOfBoundsException detail message.
     * Of the many possible refactorings of the error handling code,
     * this "outlining" performs best with both server and client VMs.
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

在這裏插入圖片描述

ArrayList中的get方法

get方法相對而言就比較簡單些:

 /**
     * 根據下標獲取指定位置的元素
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        //參數校驗,若index比數組中元素的size還大,就拋異常
        rangeCheck(index);
        //返回對應的元素
        return elementData(index);
    }
 /**
     * 在獲取數組元素之前,需要進行數據校驗
     * 若傳入的參數不在指定的數組索引範圍內,就拋異常
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

ArrayList中的remove(int index)方法

/**
     * 移除列表中指定位置的元素,然後把移除元素後面的元素向左移動
     *
     * @param index 需要移除元素的索引值
     * @return 移除的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        //參數校驗,校驗index的值是否在索引所允許的範圍內
        rangeCheck(index);
        //列表的修改次數加一
        modCount++;
        //先查出對應index位置出的元素,賦值給oldValue
        E oldValue = elementData(index);
        //把移除的元素後面的所有元素向左移動
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //把數組最後一個索引位置數據設置爲null
        elementData[--size] = null; // clear to let GC do its work
        //返回移除的元素數據
        return oldValue;
    }

在這裏插入圖片描述

ArrayList中的remove(Object o)方法

    /**
     *
     * 從列表中刪除指定元素的第一個匹配項
     *
     * @param o 需要移除的元素
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        //若元素是null,移除第一個匹配爲null的元素
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        //若元素不爲null,就移除第一個匹配到的元素
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        //列表修改次數加一
        modCount++;
        //同樣的,是把移除元素之後的所有元素向左移動
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

注意:由於ArrayList是線程不安全的,所以不要在遍歷中去對ArrayList做修改,否則會出現錯誤

ArrayList中的clear方法

/**
     * 清除列表中的所有元素
     */
    public void clear() {
        //列表修改次數加一
        modCount++;
        // clear to let GC do its work 清除所有元素,垃圾回收
        //通過遍歷把所有元素設置爲Null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

CopyOnWriteArrayList源碼解析

在這裏插入圖片描述
通過上圖可以看到copyOnWrite的實現方式,這種方式適用於讀極多,寫極少的情況,而且如果數據量巨大,在copy之後的一瞬間,內存佔用增加,也會引發問題。CopyOnWriteArrayList是線程安全的。

CopyOnWriteArrayList變量

    /** 可重入鎖 */
    final transient ReentrantLock lock = new ReentrantLock();

    /**
     * 數組,只能通過getArray和setArray操作
     */
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

CopyOnWriteArrayList的構造函數

 /**
     * 創建一個空的列表
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 構造一個包含指定元素的列表集合,按照集合返回它們的順序
     *
     * @param c 傳入的集合
     * @throws NullPointerException if the specified collection is null
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

    /**
     * 創建包含給定數組副本的列表
     *
     * @param toCopyIn the array (a copy of this array is used as the
     *        internal array)
     * @throws NullPointerException if the specified array is null
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

CopyOnWriteArrayList的兩個add方法

    /**
     * 添加一個元素到列表的最後面
     *
     * @param e 需要添加到列表中的元素
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        //初始化鎖
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //獲取數組中的元素
            Object[] elements = getArray();
            //獲取數組的長度
            int len = elements.length;
            //把elements數組copy到長度爲len + 1的newElements數組中,即新的數組長度增加1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //然後把元素加到數組的末尾
            newElements[len] = e;
            //set數組的元素爲添加之後的數組
            setArray(newElements);
            return true;
        } finally {
            //解鎖
            lock.unlock();
        }
    }

    /**
     * 向指定的索引位置加入元素,加入位置後面的元素需要向右移位
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //初始化一個鎖
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //獲取數組中的元素
            Object[] elements = getArray();
            //獲取數組的長度
            int len = elements.length;
            //如果傳入的索引值不在數組所允許的範圍內,就拋異常
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            //如果插入的索引大小和數組長度一樣,那麼直接插入到數組末尾
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                //設置新數組的長度比之前的數組長度大一位,即可以插入一個元素
                newElements = new Object[len + 1];
                //先copy elements中index索引之前的元素到newElements
                System.arraycopy(elements, 0, newElements, 0, index);
                //再把elements中index之後的元素已經index中的元素copy到index右邊,即右移
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //把元素放入到指定的索引處
            newElements[index] = element;
            //設置數組的引用爲新的數組
            setArray(newElements);
        } finally {
            //解鎖
            lock.unlock();
        }
    }

向指定索引處插入元素圖解:
在這裏插入圖片描述

CopyOnWriteArrayList的兩個get方法

get方法比較簡單,不做贅述。

private E get(Object[] a, int index) {
        return (E) a[index];
    }

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

CopyOnWriteArrayList的remove方法

    /**
     * 移除列表中指定索引位置的元素,並把後續的元素向左移動
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        //初始化鎖
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //獲取數組中的元素
            Object[] elements = getArray();
            //獲取數組的長度
            int len = elements.length;
            //獲取索引處原先的舊值
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            //如果要移除的是最後一位直接移除
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //新的數組,長度比舊的數組少一位
                Object[] newElements = new Object[len - 1];
                //同樣的,//先copy elements中index索引之前的元素到newElements
                System.arraycopy(elements, 0, newElements, 0, index);
                //再把elements中index+1之後的元素向左移位
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            //返回移除後的元素
            return oldValue;
        } finally {
            //解鎖
            lock.unlock();
        }
    }

在CopyOnWriteArrayList,移除的方法還有另外兩個,實現的方法也都大同小異,都是先copy一份列表,然後加鎖去操作,移除掉元素,然後再把數組的引用指向移除後的數組即可。

到此,arrayList和CopyOnWriteArrayList源碼就結束了,上面的解釋以及註釋可能有錯誤或者不足的地方,希望指正,共同進步,多謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章