Java集合容器系列02-ArrayList

1、ArrayList介紹

    ArrayList是內部基於動態數組的List實現,他繼承AbstractList抽象類,並實現了List,RandomAccess,Cloneable和java.io.Serializable。它實現了List接口定義的全部方法,容許插入null元素,支持元素的隨機訪問,支持克隆,支持序列化所以能在網絡和本地文件系統之間經過字節流傳輸。它的功能有點相似Vector可是是非線程安全的。若是多個線程同時訪問ArrayList而且有至少一個線程對列表作告終構性修改則有線程安全問題咱們必須在外部對它作同步處理。那麼什麼是結構性修改呢?注意添加、刪除一個或者多個元素,對ArrayList容量也就是內部動態數組大小進行修改這些操做均屬於結構性修改,修改ArrayList中一個指定位置的元素值不屬於結構性修改。java

    爲了保證線程同步,咱們能夠選擇其餘集合容器,例如Vector、CopyOnWriteArrayList,或者使用Collections.synchronizedList(List list)方法包裝ArrayList爲線程安全的類。數組

    爲了提高使用ArrayList的性能,咱們應該在使用以前進行評估分配一個合理的容量,這樣能夠在插入超出初始容量數目的元素時減小沒必要要的擴容次數,由於在擴容時須要進行數組全複製對性能損耗極大。安全

2、ArrayList的數據結構

1 - ArrayList的繼承結構

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

Object網絡

    |__AbstractColllection數據結構

            |__AbstractListapp

                    |__ArrayList        dom

2 - 內部數據結構

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //序列版本號
    private static final long serialVersionUID = 8683452581122892189L;

    //ArrayList默認容量
    private static final int DEFAULT_CAPACITY = 10;

    //一個靜態不可變的空對象數組,做爲共享變量,主要用於當ArrayList初始容量爲0時讓內部動態數組指向它,這樣可減小
    //空對象數組的分配
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //靜態不可變對象數組,做爲共享的初始容量大小的空實例數組,與EMPTY_ELEMENTDATA分開使用用於觀察第一個元素被添加進
    //來,ArrayList擴容大小
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //數組用於保存ArrayList元素數據
    transient Object[] elementData;

    //ArrayList實際保存的元素數量
    private int size;

}

 

3、ArrayList的源碼分析

1 - 構造函數

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //包含初始容量大小的構造函數。基於傳入的initialCapacity初始化內部動態數組或者拋出異常
    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);
        }
    }

    //默認構造函數。主要初始化了一個默認容量大小的內部動態對象數組elementData。
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    //建立一個包含集合c全部元素的ArrayList。若是c是空集合不包含任何元素則爲elementData分配一個容量爲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;
        }
    }
}

    構造方法邏輯較爲簡單,你們能夠去研讀源碼自行理解。函數

2 - 其餘成員方法

//ArrayList內部動態數組瘦身,若實際數據量小於內部動態數組長度,則從新爲elementData分配一個大小等於實際數據量大
    //小的數組,並恰好填滿它防止當前額外的空間浪費
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

    //返回集合容量
    public int size() {
        return size;
    }

    //判斷集合是否爲空
    public boolean isEmpty() {
        return size == 0;
    }

    //返回ArrayList是否包含元素對象o
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    //正向遍歷,返回ArrayList中第一個等於對象o的元素數組索引下標,若是o爲null那麼返回第一個等於null的元素下標
    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;
    }

    //反向遍歷返回第一個等於對象o元素數組下標
    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;
    }

    //返回ArrayList的一份淺拷貝,數組元素自己不調用拷貝函數clone,而且將ArrayList改變次數modCount清零
    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);
        }
    }

    //返回一個包含全部元素數據的對象數組
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    //返回一個包含list全部元素的對象數組
    //若是數組a長度小於list元素個數,那麼新建一個指定數組a元素類型的對象數組填充list元素數據並返回
    //若是數組a長度大於或者等於list元素個數,那麼將list元素數據複製到傳入的數組a並返回
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }


    E elementData(int index) {
        return (E) elementData[index];
    }

    //獲取list對應索引位置的元素
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    //設置對應索引位置的元素
    public E set(int index, E element) {
        rangeCheck(index);

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

}

    以上成員方法較爲簡單,不單獨列出來進行分析,下面是咱們要重點分析的幾個方法:源碼分析

1)void ensureCapacity(int minCapacity)方法性能

//肯定ArrayList的容量大小
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            //若是elementData不是默認分配的初始大小的動態對象空數組,則用戶還未進行初始擴容,最小容量爲0
            ? 0
            //用於已進行初始擴容,故最小容量爲DEFAULT_CAPACITY
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    //做用與上述方法ensureCapacity(int minCapacity)一致
    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);
    }

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

    ensureCapacity用於肯定ArrayList的容量。首先他會比較方法傳入的指定容量參數minCapacity和ArrayList的默認容量DEFAULT_CAPACITY取兩個最大值。而後modCount(ArrayList修改計數器)加1,若是指定的擴容容量minCapacity小於等於當前內部動態對象數組elementData的大小說明當前數組空間未溢出不須要進行擴容,不然進行擴容,擴容規則以下:

首先定義一個初始擴容以後的容量newCapacity=ArrayList原來的容量*1.5,取用戶指定容量minCapacity和擴容以後容量newCapacity的較小值並賦值給newCapacity,若是newCapacity大於數組的最大限制Integer.MAX_VALUE-8,那麼進行極限擴容,newCapacity=Integer.MAX_VALUE。爲elementData分配一個數組長度newCapacity的對象數組,並把原有數據填充進去。

2)boolean add(E e)與boolean addAll(Collection<? extends E> c) 方法

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


    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)用於在ArrayList中新增一個元素。它會先調用ensureCapacityInternal(size + 1)方法判斷新增元素以後數組空間是否已滿須要對內部存儲元素數據的動態數組elementData進行擴容,若是須要則進行擴容。而後在數組elementData當前最近的空閒位置存儲指定元素e

    addAll方法與add方法邏輯類似,區別只在於往ArrayList插入元素的時候,add方法在數組尾部位置設置元素,addAll方法是將傳入的容器對象c中包含的全部元素一次性複製到ArrayList數組剩餘空閒部分。

3)void add(int index, E element)與boolean addAll(int index, Collection<? extends E> c)方法

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


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

       add(int index, E element)方法用於往ArrayList指定位置index處插入元素element,插入元素以後須要將原ArrayList中index以及index後面的元素右移。方法的主要邏輯是:

一、調用rangeCheckForAdd(index)方法校驗當前傳入的索引位置index是否合法,不合法拋出數組越界異常IndexOutOfBoundException;

二、調用ensureCapacityInternal(size + 1)肯定新元素插入以後ArrayList的容量須要的時候進行擴容;

三、將原ArrayList指定位置index處空間讓出來存放指定元素e,原index以及後面位置元素使用系統數組複製函數複製到index以後的數組部分也就是右移;

四、在指定索引位置設置元素e,元素個數計數器size+1。

    boolean addAll(int index, Collection<? extends E> c)方法邏輯與add(int index, E element)類似,不一樣之處在於add是插入單個元素addAll是插入一個容器中的全部元素,所以addAll方法在肯定ArrayList的容量(基於原有元素數目加上新增集合元素數目,在必要時進行擴容)以後addAll方法右移的起始位置是擴容以後原ArrayList元素個數+插入的集合c的元素個數對應的數組下標,而在右移以後須要將集合c中的全部元素設置到擴容後elementData中間的空閒部分,這裏是經過系統數組複製完成的而add只是設置單個元素。

 

4)E remove(int index)與void fastRemove(int index)方法

public E remove(int index) {
        //校驗索引下標index的合法性(不能夠超出當前元素數量size)
        rangeCheck(index);
        //修改計數器加1
        modCount++;
        //獲取index對應的數組元素
        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;
    }

    private void fastRemove(int index) {
        //修改計數器加1
        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
    }
相關文章
相關標籤/搜索