java.util之一:ArrayList


ArrayList是java中的線性結構的一種表示方法,在java中使用頻率非常高,下面來一步一步分析其底層的實現。(JDK1.8)

一、構造函數

ArrayList的構造函數有三個,分別如下,

我們最常使用的的無參的構造函數,那么無參的構造函數是如何定義的那,

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

從上面看到無參構造函數,實際上是把elementData賦予了默認容量的一個數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

 private static final Object[] DEFAULTCAPACITY_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);
        }
    }

從上面可以看出首先判斷給定的初始容量是否大於0,如果大於0則使用此值生成Object數組。如果等於0則和無參構造函數是一樣的。如果小於0則拋出異常。在實際開發中強烈建議使用帶參數的構造函數(為什么下面說)。
最后一個構造函數如下,

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        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 {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

從上面的代碼中可以看出,把集合類c調用toArray方法,返回其數組賦給elementData,然后對elementData進行判斷如果長度不為0,這判斷elementData的類型是否為Object,如果不是則使用Object進行替換,統一處理成Obejct的類型,因為在java中所有的類的父類都是Object。
二、成員變量

在ArrayList中主要的成員變量有elementData和size,elementData代表底層ArrayList的存儲結構,即ArrayList是用數組來保存數據的;size指的是ArrayList中元素的個數(和elementData的長度區分開)。其他的成員變量像默認的初始容量為10。ArrayList在使用無參構造函數的時候分配內存的時候采用了懶加載的模式,即使用new ArrayList的時候底層不會分配空間,在真正向ArrayList中放元素的時候才會真正初始化數組長度。

三、常用方法

1、set/add

set和add方法的不同之處在於set方法會返回被覆蓋的值,而add則不會

set(int index,E element)

在index處插入元素element,其具體實現如下

public E set(int index, E element) {
        rangeCheck(index);

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

從上面的代碼中可以看出,首先需要判斷index是否合法,

 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

也就是說如果index大於ArrayList中元素的個數,則拋出異常,這里有個疑問ArrayList是使用數組存儲,默認長度是10,那么向ArrayList中添加一個元素后其數組的長度為10,ArrayList中的元素為1,為什么不允許index大於size插入那。
在判斷完index不大於size后,把要插入的元素放在數組中下標為index的位置,返回之前的元素。

add(E e)

向ArrayList中插入元素e返回boolean類型,表示是否插入成功。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

可以看到首先要確保數組的容量,

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

判斷elementData是否為空數組,如果是空數組返回初始容量和size+1兩個數中大的那個,這里應該返回初始容量10。
下個方法是,

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

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

如果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位(原大小的1.5倍)記為newCapacity。把要擴容到的大小和newCapacity比較,取兩者中比較大的那個作為數組的容量。把原先的數組中的元素拷貝到新的數組中。
在回頭來看add方法中的elementData[size++] = e;由於已經對數組進行了擴容,則可以把e放在size+1的位置上,返回true。

下面看add(int index,E e)

在index處插入e元素,

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

然后確定底層數組的容量,判斷是否需要擴容,如果不需要則把e插入到index的位置,這里不是使用的循環移動插入,而是進行了數組的分部分拷貝,把index前的元素和index后的元素進行拷貝,最后把e插入在index處,這種方式比循環移動插入要快(使用底層的拷貝技術)。

2、get方法

根據index取得ArrayList中的元素,

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

判斷給定的index的值比較和size的大小,如果超過了size則拋出異常,否則返回其index處的值。
3、contains(Object o)方法

判斷o是否在ArrayList中,由於ArrayList使用數組存儲元素,所以它是允許存重復值的,而contains方法返回的則是o第一次出現的位置。

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
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;
    }

可以看到ArrayList中允許存null值,且可以存多個。
如果o不是null,則從頭開始循環直到第一個為o的元素出現,返回其下標否則返回-1,最后根據indexOf的值大於0的返回true,否則false。

4、remove(int index)/remove(Object o)

從ArrayList中刪除元素,可以按照索引和元素進行刪除。按照元素刪除僅會刪除給定元素第一個的位置,其余不會刪除。

remove(int index)

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(Object o)

按照元素進行刪除,刪除ArrayList中的第一個為o的元素,

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

這里仍然把null的情況單獨處理,我們看不為null的情況的刪除,使用size遍歷整個數組,在數組中找到等於給定元素的下標,調用fastMove方法按照下標刪除,只會把o第一次出現的位置上的元素刪除,即重復的不會刪除。

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
    }

上面還是按照下標進行刪除。

未完待續。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM