Java源碼閱讀之ArrayList

@author StormMa
@date 2017-06-01


生命不息,奮鬥不止!


基於jdk1.8的ArrayList源碼分析。


實現List接口最常見的大概就四種,ArrayList, LinkedList, Vector, Stack實現,今天就着重看一下ArrayList的源碼實現。ArrayList的底層結構就是最簡單的數組,數據結構導致了它查詢快,但是增刪慢。另外官方也說了,ArrayList是線程不同步的。我覺得我有必貼一下官方文檔的描述。


Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)


開始閱讀

聲明
    /**
     * Default initial capacity.//初始化容量=10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.//空數組
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     * 也是空數組,但是第一次add的時候會自動調整容量爲DEFAULT_CAPACITY
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 數組聲明,不參與序列化。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     * 包含元素的個數,不一定等於elementData.length。。
     */
    private int size;

看完了類的屬性聲明,我們趁熱打鐵接着就去看一下構造方法!一探初始化ArrayList的究竟。

構造方法
空參數構造方法

默認的構造方法創建的elementData中的長度是1, size=0第一次add的時候會自動調整elementData.length=10。補充一點,我們要知道elementData.length != size。

    /**
     * Constructs an empty list with an initial capacity of ten
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
初始化指定容量的構造函數

這個構造函數沒什麼說的,判斷傳入的參數如果>0就初始化指定參數的數組,如果等於0,就是一個空數組咯,<0,對不起,拋出異常!

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     *         創建一個指定初始容量的數組
     */
    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);
        }
    }
創建包含指定Collection的構造函數

這個構造函數也沒什麼好說的,開始吧Collection轉換成數組並且elementData引用數組地址(淺拷貝),然後判斷數組的長度(並修改size的值),如果不爲0就深拷貝數組的內容到elementData中去,如果size還等於0的話,就創建一個空數組就行了。

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     * 創建一個包含指定Collection的數組
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        //如果傳入的Collection不爲空
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652), c.toArray()錯誤時候不返回Object[]
            if (elementData.getClass() != Object[].class)
            //拷貝
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {//如果爲空創建一個空數組
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
add*()
add(E e)

大致思路

  1. 確保在成功添加元素之後,數組的size不超過閾值。
  2. 添加元素
  3. 返回成功標誌
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

我們繼續看一下ensureCapacityInternal的實現

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

我想我應該補充一下add()方法思路的描述的第一條。確保成功添加元素之後的size不超過閾值。首先在ensureCapacityInternal方法中先判斷elementData的類型,如果是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(這個就和我們上面所述的DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化elementData.length=1, size=0, 第一次add會自動擴容成10,添加成功size變成1)。那麼此時取DEFAULT_CAPACITY(10)和當前假設添加元素成功之後的size(其實就是未添加之前的size+1)的最大值。然後進行ensureExplicitCapacity的判斷。在ensureExplicitCapacity中,先自增修改次數標識(我們無需關注這個屬性)。然後開始判斷if, if的主體翻譯過來就是: 數組已使用的數組長度+1大於當前數組長度(也就是比較size+1和element.length的大小,我們之前不是說過size != element.length嗎?size+1和element.length的比較說明了什麼呢?其實很簡單,因爲size代表的始終是已添加的元素的個數,而element.length就是數組的最大長度,如果添加成功的元素的個數(size+1) > 數組最大長度(element.length)了,那豈不jj了,所以此時要擴容了,那調用grow方法吧),我們接着看一下grow方法。


    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

代碼中,我們很容易看出擴容是按照1.5被的oldCapacity進行擴容的,如果1.5oldCapacity 還小於我們需要的最小的minCapacity(也就是size+1),那直接按照minCapacity進行擴容算了。如果newCapacity大於數組允許的最大值(Integer.MAX_VALUE-8)了,那就試着縮小一下擴容的範圍。判斷內存是否溢出,不然返回最大整型值,繼續擴容。如果這一切沒問題,那就進行數組的深拷貝,此時完成擴容並拷貝了數據。

add(int index, E element)
    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

這個方法,和add(E e)沒什麼差別,唯一多了的就是越界檢查。沒什麼好分析的。

addAll(Collection
    /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

因爲我們之前詳細分析了add(E e)這個方法,然後看addAll這個方法就很簡單(其實本來也不難哈(玩笑臉))。要添加的Collection的長度加上size判斷容量,然後進行深拷貝,修改size,返回添加的結果,完了,就這麼簡單!另外還有一個addAll(int index, Collection

get()
E get(int index)
    /**
     * Returns the element at the specified position in this list.
     *
     * @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) {
        rangeCheck(index);

        return elementData(index);
    }

    /**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.  This method does *not* check if the index is
     * negative: It is always used immediately prior to an array access,
     * which throws an ArrayIndexOutOfBoundsException if index is negative.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

沒啥好說的吧。

if index 越界: 
    throw exception
else: 
    return elementData[index]
set
E set(int index, E element)
    /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    // Positional Access Operations

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
contains()
contains(Object o)
    /**
     * Returns <tt>true</tt> if this list contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this list contains
     * at least one element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * @param o element whose presence in this list is to be tested
     * @return <tt>true</tt> if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

這個也不作分析了,很簡單的代碼。

remove()
E remove(int index)
    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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; // clear to let GC do its work

        return oldValue;
    }

刪除我就稍作一下分析吧,因爲我們都知道數組的特點就是查詢快,增刪慢的數據結構。 remove先檢查index,然後計算要移動的元素的個數,然後調用System.arraycopy方法

public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

src:源數組; srcPos:源數組要複製的起始位置;
dest:目標數組; destPos:目的數組放置的起始位置; length:複製的長度。
注意:src and dest都必須是同類型或者可以進行轉換類型的數組

很明顯,System.copyarray跳過了要刪除的元素,達到了刪除的目的。

clear()
clear()
    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

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

        size = 0;
    }

結束

本文來自我的個人博客: http://blog.stormma.me,轉載請註明出處!

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