ArrayList源碼理解

ArrayList源碼理解

導讀

LinkedList源碼理解放在一起查閱,效果更好

本文先給出ArrayList的特點,再從源碼的角度分析爲什麼會有這些特點:

  1. 對隊成員變量的分析,可以知道ArrayList的數據結構
  2. 對add()方法的分析,可以得知ArrayList添加數據的效率不高
  3. 對get()方法的分析,可以看出ArrayList查詢的效率非常高
  4. 對remove()方法的分析,可以瞭解到ArrayList刪除數據的效率不高

特點

與LinkedList比較,ArrayList有以下特點:

  1. 查詢效率高
  2. 添加和刪除的效率不高
  3. 數據結構是數組

源碼分析

成員變量

從成員變量中可以看出,ArrayList是用數組Object[]來儲存數據的

//容器默認大小
private static final int DEFAULT_CAPACITY = 10;

//空的容器
private static final Object[] EMPTY_ELEMENTDATA = {};

//容器,用來儲存數據
private transient Object[] elementData;

//標記容器的大小
private int size;

構造方法

三個重寫的構造方法都是用來初始化容器

//構造方法
public ArrayList(int initialCapacity) {
    super();
    //非法參數校驗
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    //給定數組的大小                                      
    this.elementData = new Object[initialCapacity];
}

//構造方法
public ArrayList() {
    super();
    //默認爲空數組
    this.elementData = EMPTY_ELEMENTDATA;
}

//構造方法
public ArrayList(Collection<? extends E> c) {
    //將給定的集合轉換成數組
    elementData = c.toArray();
    //此時,容器內有數據,更新size
    size = elementData.length;
    //c.toArray()出現異常的處理
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

add()的分析

從下面的代碼中可以看出,ArrayList是通過數組賦值的方式來添加數據

//添加數據
public boolean add(E e) {
    //更新一些數據(modCount),根據一些情況來擴充容器的大小
    ensureCapacityInternal(size + 1);  
    //添加數據,更新size
    elementData[size++] = e;
    return true;
}

//向指定的位置添加數據
public void add(int index, E element) {
    //非法參數校驗
    rangeCheckForAdd(index);

    //更新一些數據(modCount),根據一些情況來擴充容器的大小
    ensureCapacityInternal(size + 1); 
    //從index開始(包括),數據向後移動一位(這裏的效率低)
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //添加數據
    elementData[index] = element;
    //更新size
    size++;
}

從下面的代碼中可以看出數組的大小是動態變化的,一般擴充倍數爲1.5倍

//根據minCapacity來確定容器的大小
private void ensureCapacityInternal(int minCapacity) {
    //當前爲空容器的處理
    if (elementData == EMPTY_ELEMENTDATA) {
        //minCapacity取最大值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    //更新modCount
    modCount++;

    // 如果需要的容量大於當前的容量,擴充容器
    if (minCapacity - elementData.length > 0)
        //擴中容器的大小
        grow(minCapacity);
}

//ARRAY的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//擴中容器
private void grow(int minCapacity) {
    // 獲取當前容器的大小
    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);

    // 數據複製到新容器中,(這裏效率不高)
    elementData = Arrays.copyOf(elementData, newCapacity);
}

//處理容量巨大時的情況
private static int hugeCapacity(int minCapacity) {
    //超過int的最大值,拋出異常
    if (minCapacity < 0) 
        throw new OutOfMemoryError();

    //根據情況設置爲int的最大值或者MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

說明:ArrayList在添加數據的時,有時候會移動大量的數據,導致添加數據效率不高

get()的分析

從下面的代碼中可以看出,ArrayList在查找數據時,通過index直接鎖定內存,可以迅速地找到相應的數據

//獲取指定位置上的數據
public E get(int index) {
    //非法參數校驗
    rangeCheck(index);

    //返回數據
    return elementData(index);
}

remove()的分析

移除指定位置上的數據:remove(int index)

//移除指定位置上的數據
public E remove(int index) {
    //非法參數校驗
    rangeCheck(index);

    //更新modCount
    modCount++;
    //獲取指定的數據
    E oldValue = elementData(index);

    //計算要移動數據的個數
    int numMoved = size - index - 1;

    //根據情況去移動數據
    if (numMoved > 0)
        //從index+1開始,所有數據向前移動一位,這樣,index位置上的數據就被覆蓋了,相當於移除了
        //這裏可以看出,移除操作的效率不高
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //容器的最後一位置空,更新size
    elementData[--size] = null; 

    return oldValue;
}

說明:
1. 移除操作實際上是一個覆蓋數據的操作,是將後面一個數據覆蓋到前一個位置,如果是末尾的數據,則直接置空。
2. 與add()中的耗時操作類似:可能移動大量的數據導致低效率

移除特定的數據:remove(Object o)

//移除容器中第一個相應的數據
public boolean remove(Object o) {
    //首先對o進行空判斷,防止o.equal()報錯
    //兩部操作的邏輯一致

    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 void fastRemove(int index) {
    //更新modCount
    modCount++;
    //計算要移動數據的個數
    int numMoved = size - index - 1;
    //根據情況去移動數據
    if (numMoved > 0)
        //從index+1開始,所有數據向前移動一位,這樣,index位置上的數據就被覆蓋了,相當於移除了
        //這裏可以看出,移除操作的效率不高
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //容器的最後一位置空,更新size
    elementData[--size] = null; 
}

說明:與remove(int index)相比,多了一步遍歷查詢的操作

indexOf()的分析

//根據Object找到第一個出現的位置
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;
    }
    //沒有找,返回-1
    return -1;
}

這裏代碼與LinkedList中的類似,都是遍歷對比。效率高也僅僅是這裏鎖定了內存。

結語

瞭解了ArrayList的原理,對我們優化代碼,提升效率會有很大的幫助,這裏也給出使用建議:
如果查詢操作較多,使用ArrayList的效果更好.

LinkedList源碼理解放在一起閱讀,效果更好
轉載請標明出處http://blog.csdn.net/qq_26411333/article/details/51583376#t10

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章