程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> jdk源碼分析之ArrayList,jdk源碼arraylist

jdk源碼分析之ArrayList,jdk源碼arraylist

編輯:JAVA綜合教程

jdk源碼分析之ArrayList,jdk源碼arraylist


ArrayList關鍵屬性分析

ArrayList采用Object數組來存儲數據

    /**

     * The array buffer into which the elements of the ArrayList are stored.

     * The capacity of the ArrayList is the length of this array buffer. 何問起 hovertree.com

     */

    transient Object[] elementData; // non-private to simplify nested class access

    /**

     * The size of the ArrayList (the number of elements it contains).

     * @serial

     */

    private int size;

Object[] elementData是一個buffer數組,用來存儲ArrayList的數據,該數組的大小表示ArrayList的容量,而size屬性表示的是ArrayList裡邊存儲元素的個數。

    /**

     * 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.何問起 hovertree.com

     */

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

多個ArrayList實例共享的static屬性,一個空數組的實例,使用ArrayList的無參構造函數創建ArrayList實例的時候,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA給底層數組elementData賦值

    /**

     * Constructs an empty list with an initial capacity of ten.

     */

    public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

而當創建一個容量為0的ArrayList時,直接將層數組elementData賦值為另外一個static屬性

    /**

     * Shared empty array instance used for empty instances.

     */

    private static final Object[] EMPTY_ELEMENTDATA = {};

    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獲取數據get(int index)

ArrayList可以通過下標對數據進行隨機訪問,時間復雜度O(1),實現方法為get方法

    public E get(int index) {

        rangeCheck(index);

        return elementData(index);

    }

get方法很簡單,輸入參數合法的話直接返回底層數組elementData對應位置的元素即可。 
而rangeCheck主要是檢查下標不能越界訪問

    private void rangeCheck(int index) {

        if (index >= size)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

向ArrayList尾部添加一個數據add(E e)

添加數據有兩個方法,調用add(E e) 向ArrayList末尾添加數據和調用add(int index, E element)添加數據到指定位置

    public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e;

        return true;

}

添加數據首先檢查是不是需要擴充容量來添加新的數據,調用ensureCapacityInternal(size + 1)確保當前容量足夠添加一個新的數據,然後elementData[size++] = e將新數據添加在數組的末尾

    private void ensureCapacityInternal(int minCapacity) {

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

        }

        ensureExplicitCapacity(minCapacity);

 }

ensureCapacityInternal確保在ArrayList容量為0的時候添加數據擴容時,至少擴容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小默認為10

    /**

     * Default initial capacity.

     */

    private static final int DEFAULT_CAPACITY = 10;

然後調用 ensureExplicitCapacity(minCapacity)進行擴容

    private void ensureExplicitCapacity(int minCapacity) {

        modCount++;

        // overflow-conscious code

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

modCount++用於記錄修改次數,主要用於一個線程使用迭代器迭代數據的時候另一個數據更改ArrayList結構拋出ConcurrentModificationException異常 
if (minCapacity - elementData.length > 0)用於判斷是否真的需要擴容,elementData.length表示的是容器容量,size表示容器存儲數據的數量,而minCapacity =sie+1,因此elementData如果有多於一個位置空閒沒有存儲數據,就不需要擴容 
否則調用grow(minCapacity)進行真正的擴容

    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);

    }

可以看到擴容時直接將容量大小變為之前的1.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);

這裡還需要對newCapacity進行調整,如果擴容後的newCapacity還是比minCapacity小 
滿足

if (newCapacity - minCapacity < 0)

設置newCapacity為傳進來的參數minCapacity

newCapacity = minCapacity;

然後判斷現在的newCapacity是否比上限MAX_ARRAY_SIZE大

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

如果newCapacity比MAX_ARRAY_SIZE還大,則需要調用hugeCapacity(minCapacity)判斷是否是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;

    }

hugeCapacity(int minCapacity)首先檢測是否溢出,沒溢出的話就返回Integer.MAX_VALUE 作為最大值(minCapacity > MAX_ARRAY_SIZE條件在調用出就滿足)

經過上述一系列步驟,最終滿足各種條件的新容量值minCapacity得到滿足

   // minCapacity is usually close to size, so this is a win:

    elementData = Arrays.copyOf(elementData, newCapacity);

這裡是直接創建一個容量為newCapacity的新數組,把之前elementData保存的數據復制到新數組。數組的復制是比較耗費性能的,因此應該避免連續擴容,盡量調用有參構造函數ArrayList(int initialCapacity)並設置合理容量大小

向ArrayList任意位置添加一個數據add(int index, E element)

調用add(int index, E element)在ArrayList任意位置添加數據

    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++;

    }

傳入參數合法性檢查仍然是方法體第一件要做的事情,這裡要檢測插入數據的位置index是否合法

    private void rangeCheckForAdd(int index) {

        if (index > size || index < 0)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

 }

參數合法性通過後調用ensureCapacityInternal(size + 1)進行擴容,上邊已經分析過擴容的固定套路。擴容後就是調用System.arraycopy進行數組的復制,由此可見ArrayList添加數據是比較耗費性能的,事件復雜度是O(n),因為插入數據總是伴隨著數組的復制(數組元素的移動)。

在ArrayList尾部添加一個Collection集合addAll

    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;

    }

先通過Collection的toArray方法把Collection轉化為Object數組,然後通過ensureCapacityInternal(size + numNew)進行擴容,然後調用

System.arraycopy(a, 0, elementData, size, numNew); 
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);

System.arraycopy進行數組的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示從數組a的下標0出開始復制length個元素導數組elementData的下標size處。

在ArrayList任意位置添加一個集合Collection

 public boolean addAll(int index, Collection<? extends E> c) {

        rangeCheckForAdd(index);

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;

        if (numMoved > 0)

            System.arraycopy(elementData, index, elementData, index + numNew,

                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);

        size += numNew;

        return numNew != 0;

}

仍然是先做入口參數的合法性檢查

    private void rangeCheckForAdd(int index) {

        if (index > size || index < 0)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    }

然後把集合轉化為Object數組

Object[] a = c.toArray();

通過ensureCapacityInternal(size + numNew)進行擴容 
計算原數組需要移動的數據的個數int numMoved = size - index 
如果有數據需要移動,調用System.arraycopy進行數據整體移動

System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

從elementData數組下標為index出復制numMoved個數據到elementData數組的下標為index + numNew處 
這樣elementData數組下標為index到index+numNew-1的位置被空出來,用於放置新的數據 
然後集合的數據添加到elementData數組留出的位置即可

System.arraycopy(a, 0, elementData, index, numNew);

從數組a的下標為0的地方復制numNew個數據到elementData數組的下標為index處

ArrayList中獲取某一數據的下標indexOf

    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;

    }

獲取下標只能通過遍歷的方式逐一比較,null數據不能調用方法,所以不能通過equals進行比較,所以分類討論, 
如果傳入的參數o是是null的話通過elementData[i]==null進行比較,否則通過o.equals(elementData[i])進行比較。 
如果遍歷過程中找到該數據,返回該數據下標,遍歷結束沒有找到返回-1 
注意indexOf是找到第一個相等的數據就直接返回下標了,如果想找到該數據在ArrayList中的最後一個位置,使用lastIndexOf即可

清空ArrayList的全部數據clear()

    public void clear() {

        modCount++;

        // clear to let GC do its work

        for (int i = 0; i < size; i++)

            elementData[i] = null;

        size = 0;

    }

首先modCount++,這是如果其他線程正在通過迭代器進行數據的訪問,檢測到modCount發生了變化將會拋出ConcurrentModificationException 
然後設置數組中的每一個元素為null,方便垃圾回收,最後設置size=0; 
雖然數據全部置null,size歸0,但是elementData的大小沒有變化

ArrayList的克隆clone()

    /**

     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The

     * elements themselves are not copied.)

     *

     * @return a clone of this <tt>ArrayList</tt> instance

     */

    public Object clone() {

        try {

            ArrayList<?> v = (ArrayList<?>) super.clone();

            v.elementData = Arrays.copyOf(elementData, size);

            v.modCount = 0;

            return v;

        } catch (CloneNotSupportedException e) {

            // this shouldn't happen, since we are Cloneable

            throw new InternalError(e);

        }

    }

可以看到clone方法只是進行了淺克隆,在某些需要深克隆的場景出,需要自己負責每一個元素的深克隆

ArrayList刪除指定位置的數據

    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;

}

代碼流程還是比較清晰 
下標檢查,modCount++達到迭代器快速失敗,數組元素的移動,返回舊值 
其中比較重要的一點就是

elementData[–size] = null; // clear to let GC do its work

Effective Java提到了這點,自己申請內存自己要記得管理,否則造成內存洩露

ArrayList刪除某一個數據

    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;

    }

這裡同樣是根據傳入的參數o是否為null進行分類處理,找到元素所在的位置index後直接調用 fastRemove(index)進行數據的刪除,fastRemove比remove不需要檢查數組下標

ArrayList的排序sort

    public void sort(Comparator<? super E> c) {

        final int expectedModCount = modCount;

        Arrays.sort((E[]) elementData, 0, size, c);

        if (modCount != expectedModCount) {

            throw new ConcurrentModificationException();

        }

        modCount++;

 }

先保存modCount,如果modCount在排序過程中被改變,拋出ConcurrentModificationException異常 
排序是直接調用Arrays.sort方法

ArrayList的縮容trimToSize()

    public void trimToSize() {

        modCount++;

        if (size < elementData.length) {

            elementData = (size == 0)

              ? EMPTY_ELEMENTDATA

              : Arrays.copyOf(elementData, size);

        }

   }

先登記modCount++是迭代器可以快速失敗 
然後通過 Arrays.copyOf進行數組創建和復制,使ArrayList的容量等於保存的數據的數量的大小

推薦:http://www.cnblogs.com/roucheng/p/javatimu.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved