天天看點

ArrayList源碼解析

ArrayList是基于List 接口,大小可變數組的實作。實作了所有可選清單操作,并允許包括 null 在内的所有元素。除了實作 List 接口外,此類還提供一些方法來操作内部用來存儲清單的數組的大小。下面我們來分析ArrayList的源代碼,ArrayList繼承體系結構如下:

public class ArrayList<E> extends AbstractList<E>

implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList類繼承AbstractList類并實作了List、Cloneable和Serializable接口,因而ArrayList具有克隆和序列化的功能。

屬性

//預設初始容量

private static final int DEFAULT_CAPACITY = 10;

//用于空執行個體的共享空數組執行個體

private static final Object[] EMPTY_ELEMENTDATA = {};

//用于預設大小的空執行個體的共享空數組執行個體。與EMPTY_ELEMENTDATA數組的差別在于當第一個元素被加入進來的時候,它知道擴大多少

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//elementData數組用來存儲ArrayList中的元素

transient Object[] elementData;

//ArrayList中存儲的元素的個數

private int size;

構造函數

ArrayList提供了三種方式的構造器:

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

源碼的注釋是構造一個初始容量為 10 的空清單。即當我們不提供參數而new一個對象時,底層的數組就是直接用長度為10的空執行個體數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA進行執行個體化。難點就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA的長度我們還不知道,關于如何使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA構造了一個初始容量為10的空清單在下面的add(E e)函數源碼的介紹中就會知道答案了。

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

傳入參數來執行個體化底層的數組對象,其中對參數的3中情況進行了處理。當參數小于0時抛異常;當參數等于0時;用空執行個體數組對象EMPTY_ELEMENTDATA來初始化底層數組elementData。

public ArrayList(Collection<? extends E> c) {  
    elementData = c.toArray();  
    size = elementData.length;  
    // c.toArray might (incorrectly) not return Object[] (see 6260652)  
    if (elementData.getClass() != Object[].class)  
        elementData = Arrays.copyOf(elementData, size, Object[].class);  
}            

将容器Collection轉化為數組賦給數組elementData,還對Collection轉化是否轉化為了Object[]進行了檢查判斷。如果Collection為空,則就将空的執行個體數組對象EMPTY_ELEMENTDATA賦給了elementData。

常用方法

修改

// 用指定的元素替代此清單中指定位置上的元素,并傳回以前位于該位置上的元素。  
public E set(int index, E element) {  
    RangeCheck(index);  
  
    E oldValue = (E) elementData[index];  
    elementData[index] = element;  
    return oldValue;  
}            

添加

// 将指定的元素添加到此清單的尾部。  
public boolean add(E e) {  
    ensureCapacityInternal(size + 1);   
    elementData[size++] = e;  
    return true;  
} 

// 将指定的元素插入此清單中的指定位置。  
// 如果目前位置有元素,則向右移動目前位于該位置的元素以及所有後續元素(将其索引加1)。  
public void add(int index, E element) {  
    //位置有效性檢查
    rangeCheckForAdd(index);
    //添加修改次數以及判斷是否需要擴張數組長度
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //完成數組自身從index開始的所有元素拷貝到index+1開始且長度為size-index的位置上。
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
} 

// 按照指定collection的疊代器所傳回的元素順序,将該collection中的所有元素添加到此清單的尾部。  
public boolean addAll(Collection<? extends E> c) {  
    Object[] a = c.toArray();  
    int numNew = a.length;  
    ensureCapacityInternal(size + numNew);  // Increments modCount 
    //将a中的所有元素拷貝到數組elementData最後一個元素的後面 
    System.arraycopy(a, 0, elementData, size, numNew);  
    size += numNew;  
    return numNew != 0;  
} 

// 從指定的位置開始,将指定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, size, numNew);  
    size += numNew;  
    return numNew != 0;  
}           

讀取

public E get(int index) {
        rangeCheck(index);//有效性檢查
        return elementData(index);
    }           

删除

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;
    }
// 移除此清單中首次出現的指定元素(如果存在)。這是應為ArrayList中允許存放重複的元素。
public boolean remove(Object o) {
// 由于ArrayList中允許存放null,是以下面通過兩種情況來分别處理。
        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,都是在ArrayList中找到與此第一個相等的元素的位置,然後調用fastRemove(index)來進行移除;如果沒有找到指定對象o的位置,則傳回false,表示沒有移除成功。

需要注意的是從數組中移除元素的操作,也會導緻被移除的元素以後的所有元素的向前移動一個位置。remove(int index)從代碼就可以看到,而 remove(Object o)則是調用了fastRemove(index),我們看下代碼:

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
}           

可以看到,我們需要左移的功能是在這裡實作的

清除

//直接将數組中的所有元素設定為null即可,這樣便于垃圾回收
public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }           

擴容

擴容需要要重點說明的一個,在添加方法中涉及到ensureCapacityInternal()方法,源碼如下:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
}           

如果elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,則就用預設容量10來進行開辟空間。這裡的源碼就解釋了DEFAULTCAPACITY_EMPTY_ELEMENTDATA數組和EMPTY_ELEMENTDATA數組的差別之所在。也說明了當我們用無參構造函數來執行個體化一個對象時,确實是構造的一個長度為10的數組對象。

接下來再看下ensureExplicitCapacity()方法

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

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

接着看grow()方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        /*
        下面兩個if的作用為處理兩種情況:
        1)第一種情況為:如果newCapacity擴充的過小。則應該至少擴張到所需的空間大小minCapacity
        2)第二種情況為:newCapacity擴張的過大,如果過大,則用Integer.MAX_VALUE來代替。
         */
        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倍。 

但是,由于擴張1.5倍可能和我們的需要不一緻,即可能太小,也可能太大。是以,就有了源碼中的兩個if條件的處理。即如果擴張1.5倍太小,則就用我們需要的空間大小minCapacity來進行擴容。如果擴張1.5倍太大或者是我們所需的空間大小minCapacity太大,則進行Integer.MAX_VALUE來進行擴容。

參考:

http://zhangshixi.iteye.com/blog/674856 https://blog.csdn.net/u010412719/article/details/51108357 https://blog.csdn.net/ns_code/article/details/35568011