Jdk1.8 Collections Framework源碼解析(1)-ArrayList

在工做中Java的集合類是咱們常常會遇到的,最近閒下來了,就把相關知識作一個總結,方便本身理解。 打開java.util.ArrayList的源碼,線上的 openJdk源碼能夠查看 這裏。 看一個源碼的類,首先八一八該類的做者是@author Josh Bloch 和 @author Neal Gafter 這兩位老兄(有興趣的猿們能夠,八卦一下他們的故事,其中Josh Bloch 但是著名的Effective Java的做者,絕對牛掰,值得拜讀吧!)。java

好的,言歸正轉,首先咱們看一個類首先要看看這個是幹什麼的, 有什麼特性,這些都在實現接口上有體現(好廢話,不過也有不少小白不知道如何看的,我當初就是那麼中的一個,囧),好,咱們直接從最頂級的接口看吧。如何知道他的頂級接口是誰(我廢話了),看看:算法

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

經過看到這個類的接口實現,咱們點擊List<E> , 直到接口的頂層,咱們就能看到這樣的接口實現。數組

package java.lang;  
import java.util.Iterator;  
  
public interface Iterable<T> {  
    Iterator<T> iterator();  
}

因爲篇幅的關係,去掉了註釋。註釋上說明,實現了這個接口的類,可使用"foreach"語法。 如今jdk8 大部分循環都開始隱藏Iterator的實現方式,支持foreach 的遍歷實現。安全

接下來,咱們從頂級接口開始往下八卦一下,看到的接口是java.util.Collection:app

package java.util;  
public interface Collection<E> extends Iterable<E> {  
    int size();  
    boolean isEmpty();  
    boolean contains(Object o);  
    Iterator<E> iterator();  
    Object[] toArray();  
    <T> T[] toArray(T[] a);  
    boolean add(E e);  
    boolean remove(Object o);  
    boolean containsAll(Collection<?> c);  
    boolean addAll(Collection<? extends E> c);  
    boolean removeAll(Collection<?> c);  
    boolean retainAll(Collection<?> c);  
    void clear();  
    boolean equals(Object o);  
    int hashCode();  
}

看過《算法4》這本書的人,就會以爲這大竄的balabala 的好熟悉吧。源碼裏面的註釋很詳細,反正大概意思是說(英語不太好),這是集合層次的頂級接口,表明了一組對象blablablabla,全部通用的實現應該提供兩個"標準"的構造方法:無參的構造方法和擁有一個集合類型參數的構造方法blablablabla,總之這個接口對集合進行了高度滴、抽象滴定義:)dom

接下來就是java.util.List接口了。函數

public interface List<E> extends Collection<E> {  
    //和Collection重複的方法就不貼了,但必須知道有這些方法  
    boolean addAll(int index, Collection<? extends E> c);  
    E get(int index);  
    E set(int index, E element);  
    void add(int index, E element);  
    E remove(int index);  
    int indexOf(Object o);  
    int lastIndexOf(Object o);  
    ListIterator<E> listIterator();  
    ListIterator<E> listIterator(int index);  
    List<E> subList(int fromIndex, int toIndex);  
}

list能夠經過index來訪問和查找集合內的元素,集合內元素是可重複的,由於index(集合中的位置)不一樣。工具

還有一個ArrayList實現的接口是java.util.RandomAccess:性能

package java.util;  
public interface RandomAccess {  
}

一看沒有方法的接口,就知道這大概是個標記接口嘍。實現這個接口的集合類會在隨機訪問元素上提供很好的性能。好比說,ArrayList實現了RandomAccess而LinkedList沒有,那麼當咱們拿到一個集合時(假設這個集合不是ArrayList就是LinkedList),若是這個集合時ArrayList,那麼咱們遍歷集合元素可使用下標遍歷,若是是LinkedList,就使用迭代器遍歷。好比集合工具類Collections中提供的對集合中元素進行二分查找的方法,代碼片斷以下:ui

public static <T>  
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {  
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)  
            return Collections.indexedBinarySearch(list, key);  
        else  
            return Collections.iteratorBinarySearch(list, key);  
}

還有java.lang.Cloneable和java.io.Serializable兩個標記接口,表示ArrayList是可克隆的可序列化的

還要看一下AbstractCollection和AbstractList,這兩個抽象類裏實現了一部分公共的功能,代碼都還比較容易看懂。

須要注意的地方是AbstractList中的一個屬性modCount。這個屬性主要由集合的迭代器來使用,對於List來講,能夠調用iterator()和listIterator()等方法來生成一個迭代器,這個迭代器在生成時會將List的modCount保存起來(迭代器實現爲List的內部類),在迭代過程當中會去檢查當前list的modCount是否發生了變化(和本身保存的進行比較),若是發生變化,那麼立刻拋出java.util.ConcurrentModificationException異常,這種行爲就是fail-fast。

好了,終於能夠看ArrayList了,在看以前能夠先思考一下,若是咱們本身來實現ArrayList,要怎麼實現。

思考中..............

首先,咱們都會把ArrayList簡單的看做動態數組(區別於LinkedList的鏈表實現),說明咱們使用它的時候大都是當作數組來使用的,只不過它的長度是可改變的。那咱們的問題就變成了一個實現長度可變化的數組的問題了。

那麼首先,咱們會用一個數組來作底層數據存儲的結構,新建的時候會提供一個默認的數組長度。當咱們添加或刪除元素的時候,會改變數據的長度(當默認長度不夠或者其餘狀況下),這裏涉及到數據的拷貝,咱們可使用系統提供的System.arraycopy來進行數組拷貝。若是咱們每次添加或者刪除元素時都會由於改變數組長度而拷貝數組,那也太2了,能夠在當數組長度不夠的時候,擴展必定的空間,好比能夠擴展到原來的2倍,這樣會提升一點性能(若是不是在數組末尾添加或刪除元素的話,仍是會進行數組拷貝),但同時浪費了一點空間。再看看上面的接口,好比咱們要實現size方法,那麼就不能直接返回數組的長度了(因爲上面的緣由),也沒辦法去遍歷數組獲得長度(由於可能存在null元素),咱們會想到利用一個私有變量來保存長度,在添加刪除等方法裏面去相應修改這個變量(這裏不考慮線程安全問題,由於ArrayList自己就不是線程安全的)。

帶着這些問題,來看一下ArrayList的代碼。 首先,真正存儲元素的是一個數組。

/**
     * 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 == EMPTY_ELEMENTDATA will be expanded to
     * DEFAULT_CAPACITY when the first element is added.
     */
    private transient Object[] elementData;

那麼這個數組該怎麼初始化呢?數組的長度該怎麼肯定呢?看一下構造方法。

/**
     * 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) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

能夠看到,咱們能夠選擇使用一個參數做爲ArrayList內部數組的容量來構造一個ArrayList;或者使用無參的構造方法,這樣會默認使用10做爲數組容量。咱們在實際應用中能夠根據具體狀況來選擇使用哪一個構造方法,例如在某種特定邏輯下,咱們須要存儲200個元素(固定值)到一個ArrayList裏,那咱們就可使用帶一個int型參數的構造方法,這樣就避免了在數組長度不夠,進行擴容時,內部數組的拷貝。

固然,根據接口java.util.Collection的規範(非強制),ArrayList也提供用一個集合作爲參數的構造方法。

/**
     * 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
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

上面說到說考慮用一個私有變量來存儲size(集合中實際原始的個數)。在ArrayList也有這樣一個變量。

/** 
 * The size of the ArrayList (the number of elements it contains). 
 * 
 * @serial 
 */  
private int size;

有了這個變量,一些方法實現起來就很是簡單了。就是教科書般的,函數了。

/** 
 * Returns the number of elements in this list. 
 * 
 * @return the number of elements in this list 
 */  
public int size() {  
    return size;  
}  
  
/** 
 * Returns <tt>true</tt> if this list contains no elements. 
 * 
 * @return <tt>true</tt> if this list contains no elements 
 */  
public boolean isEmpty() {  
    return size == 0;  
}  
  
/** 
 * 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;  
}  
  
/** 
 * Returns the index of the last occurrence of the specified element 
 * in this list, or -1 if this list does not contain the element. 
 * More formally, returns the highest 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 lastIndexOf(Object o) {  
    if (o == null) {  
        for (int i = size-1; i >= 0; i--)  
     if (elementData[i]==null)  
         return i;  
    } else {  
        for (int i = size-1; i >= 0; i--)  
     if (o.equals(elementData[i]))  
         return i;  
    }  
    return -1;  
}

此外,還有維護數組的的一些函數,也是比較容易理解的。

/**
     * 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);
    }

    /**
     * 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;
    }

    /**
     * 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;
    }

    /**
     * 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++;
    }

    /**
     * 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;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

    /*
     * 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
    }

    /**
     * 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;
    }

有幾個要注意的地方,首先是RangeCheck方法,顧名思義是作邊界檢查的,看看方法實現。

/**
     * 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));
    }

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

看看,它拋出的事 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 你們犯過這個錯,見過這個異常的人很多吧,常遇到的,哈哈,囧逼了,這樣咱們就能夠理解咱們平時遇到這個異常的緣由了。

還有就是在"添加"方法中的ensureCapacity方法,上面也考慮到擴容的問題,這個方法其實就幹了這件事情。
/**
    * Increases the capacity of this <tt>ArrayList</tt> instance, if
    * necessary, to ensure that it can hold at least the number of elements
    * specified by the minimum capacity argument.
    *
    * @param   minCapacity   the desired minimum capacity
    */
   public void ensureCapacity(int minCapacity) {
       int minExpand = (elementData != EMPTY_ELEMENTDATA)
           // any size if real element table
           ? 0
           // larger than default for empty table. It's already supposed to be
           // at default size.
           : DEFAULT_CAPACITY;

       if (minCapacity > minExpand) {
           ensureExplicitCapacity(minCapacity);
       }
   }

   private void ensureCapacityInternal(int minCapacity) {
       if (elementData == 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);
   }

   /**
    * The maximum size of array to allocate.
    * Some VMs reserve some header words in an array.
    * Attempts to allocate larger arrays may result in
    * OutOfMemoryError: Requested array size exceeds VM limit
    */
   private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

   /**
    * 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;
   }

最後再看一下另外一個刪除方法。

/**
     * 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;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

    /*
     * 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
    }

能夠看到,像size、isEmpty、get、set這樣的方法時間複雜度爲O(1),而像indexOf、add、remove等方法,最壞的狀況下(如添加元素到第一個位置,刪除第一個位置的元素,找最後一個元素的下標等)時間複雜度爲O(n)。

通常狀況下內部數組的長度老是大於集合中元素總個數的,ArrayList也提供了一個釋放多餘空間的方法,咱們能夠適時調用此方法來減小內存佔用。

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = Arrays.copyOf(elementData, size);
        }
    }

最後,別忘了它是線程不安全的。

參考:

http://brokendreams.iteye.com/blog/1913619

推薦一些資源:

相關文章
相關標籤/搜索