源碼系列(一)ArrayList 源碼解析

一、ArrayList定義

ArrayList 是一個數組隊列,至關於 動態數組。與Java中的數組相比,它的容量能動態增加。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。java

ArrayList 繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。 ArrayList 實現了RandmoAccess接口,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,爲List提供快速訪問功能的。在ArrayList中,咱們便可以經過元素的序號快速獲取元素對象;這就是快速隨機訪問。稍後,咱們會比較List的「快速隨機訪問」和「經過Iterator迭代器訪問」的效率。數組

ArrayList 實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。安全

ArrayList 實現java.io.Serializable接口,這意味着ArrayList支持序列化,能經過序列化去傳輸。bash

和Vector不一樣,ArrayList中的操做不是線程安全的!因此,建議在單線程中才使用ArrayList,而在多線程中能夠選擇Vector或者CopyOnWriteArrayList。數據結構

二、ArrayList源碼分析

ArrayList 有如下三種初始化的方法,此爲1.8.0的源碼,和以前的源碼有所區別,在以前的源碼中,默認狀況下,初始容量爲10,而在1.8的源碼中,默認狀況下則爲多線程

/**
     * 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.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
複製代碼
/**
     * 指定一個初始容量的構造方法
     *
     * @param  ArrayList的初始容量
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        //若是初始容量>0 ,則以指定的值爲初始容量
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
      //若是指定的值爲0,則直接賦值爲空數組
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     *在此方法中則默認賦值一個DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空數組,此數組和EMPTY_ELEMENTDATA 區分開來,用來在添加第一個元素的時候,肯定要擴容多少
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     *構造一個包含指定元素的list,這些元素的是按照Collection的迭代器返回的順序排列的
     *
     * @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();
        if ((size = elementData.length) != 0) {
            //若是是非空的集合,則直接拷貝進入當前數組
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 若是傳入的數據爲空,則使用默認的空數組代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
複製代碼

##三、增刪改查dom

add方法

/**
     * 直接在集合的末尾添加一個元素
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
複製代碼

add方法分紅了兩步進行,函數

判斷並擴充容量( ensureCapacityInternal(size + 1))+賦值( elementData[size++] = e)

從代碼中能夠看出,進行添加數據操做的時候,會先判斷當前底層的數組是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,這個數組在構造方法 ArrayList() 中被賦值,用來區別於EMPTY_ELEMENTDATA ,在第一次進行數組操做的時候進行判斷,從源碼能夠看出,會和默認的容量10進行比較,取較大的值,相對於以前直接 賦值爲 10 的容量,我的以爲有兩點優點:一、初始化的時候不會直接開闢空間 二、減小了一次擴容操做;效率上有必定的提高,設計仍是比較巧妙的源碼分析

private void ensureCapacityInternal(int minCapacity) {
       //若是調用的是 參數爲空的構造方法,第一次操做的時候,就判斷一下,
       //若是此時請求的容量大於默認初始容量10,取請求的容量,不然爲默認容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //明確容量
        ensureExplicitCapacity(minCapacity);
    }

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

        // 若是請求的最小容量大於數組長度,則進行一次擴容,以儲存全部數據
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//增長容量,以確保能容納mincapacity指定的元素
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //擴容,新的容量爲原來的 1.5倍
        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:
        //將老數據copy到新的數組中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
複製代碼

####add 的其餘方法 從源碼能夠很明確的看出來,和add(E e)很是接近,僅在copy數據的時候有所差異性能

public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //和add(E e)方法的區別僅這一行代碼,擴容以後,將老數據在從指定位置(index)開始
      //以後的數據都向後移動一位,而後將index位置的賦值爲 e
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

//和add 方法相似,區別在於將傳入的集合轉化成數組,而後總體位移copy
 public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        //擴容判斷
        ensureCapacityInternal(size + numNew);  // Increments modCount
      //將傳入的數據copy進入數組
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

複製代碼

刪除 remove方法

移出數據有 remove(int index) remove(Object o) ,兩個方法本質都是根據index,直接利用數組的下標刪除指定數據,同時會將數據進行位移,填補空缺

public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) 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;
    }

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

複製代碼

查找 get(int index)

ArrayList底層是數組,所以查找就很是簡單,直接根據數組下標找到數據

public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }
複製代碼

更改 set(int index, E element)

更改也是很是簡單,直接根據數組下標更改數據

public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }
複製代碼

增刪改查小結

從上面的源碼能夠很明顯的看出,ArrayList的增刪改查操做實質上就是對底層數組的操做,新增的時候,都須要對數組進行擴容+copy操做,刪除也須要對數組進行copy,因此ArrayList的增長和刪除效率會很是低,可是相對的,得利於底層的數組結果,在進行查找和更改操做的時候,能夠根據下標直接進行操做,只有O(1)的複雜度,所以在查找需求比較頻繁的操做中,可使用ArrayList,極大的增長操做效率,可是在增刪比較頻繁的時候,就須要考慮其餘的數據結構了; 同時,合理的使用ArrayList的構造方法,例如在初始化的時候,若是已經知道當前數據的大小,能夠直接使用ArrayList(int initialCapacity) 構造方法指定初始容量,這樣能夠避免在添加數據的時候頻繁擴容下降性能,同時也能夠避免1.5倍擴容機制形成的空間浪費

相關文章
相關標籤/搜索