天天看點

java ArrayList源碼分析

研究ArrayList源碼有一段時間了,說實話收益還是很大的。主要當方法的實作比較清楚時,用起來也比較放心。并且集合相關的源碼對了解資料結構還是有點幫助的。話不多說,開始分析。

(1)ArrayList實作了List接口,List接口中定義了list中通用的很多方法。

public class ArrayList<E> extends AbstractList<E>
	implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	...<span style="font-family: Arial, Helvetica, sans-serif;">}</span>
           

(2)ArrayList裡的屬性。其中elementData就是底層用來存儲ArrayList中元素的具體數組。

private static final long serialVersionUID = 8683452581122892189L;
    //預設初始化容量
    private static final int DEFAULT_CAPACITY = 10;
    //當不指定ArrayList大小時,elementdata<span style="font-family: Arial, Helvetica, sans-serif;">預設</span>初始化<span style="font-family: Arial, Helvetica, sans-serif;">是一個空的數組</span>
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //存儲ArrayList裡元素的數組<span style="white-space:pre">	</span>
    private transient Object[] elementData;
    //包含元素的數量
    private int size;
    //數組最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
           

(3)構造方法

相比于之前的ArrayList的實作,之前當不指定ArrayList初始化容量時,預設在調用構造方法時,初始化elementData容量為DEFAULT_CAPACITY(即10)。現在隻會在調用add方法第一次添加元素時,會初始化容量。起到一點節約記憶體的作用。

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;//當不傳入大小時,預設是一個空的數組
    }
	//根據其他集合構造List
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        if (elementData.getClass() != Object[].class)//toArray傳回的可能不是Object[]類型
            elementData = Arrays.copyOf(elementData, size, Object[].class);//[1]調用該方法傳回Object[]數組
    }
           

以上ArrayList(Collection<? extends E> c)這個構造方法根據傳入的集合建立ArrayList。當傳入的集合調用toArray方法傳回的數組類型不是Object[]數組時,在[1]處調用Arrays工具類中的copyOf方法傳回一個Object[]類型的數組并賦給elementData,由此達到建立ArrayList的目的。

Arrays類中的copyOf方法

[1].Arrays類中的copyOf方法
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
           

(4)boolean add(E e)方法:該方法用于向list末尾添加一個指定類型的元素。每次添加元素時,需要調用ensuerCapacityInternal方法來確定elementData數組的内部容量足夠存儲新添加的元素e。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!![2]
        elementData[size++] = e;//添加元素e,随後size+1
        return true;
    }
           

ensureCapacityInternal方法如下,該方法隻會對elementData數組容量為0時(即無參構造方法中this.elementData = EMPTY_ELEMENTDATA;),指定elementData的最小容量為DEFAULT_CAPACITY或add中傳入的大小中的較大者。如果elementData數組大小不為0,則會繼續調用ensureExplicitCapacity方法(確定數組的明确容量)。

[2]處調用ensureCapacityInternal(size+1)保證每次添加元素時數組容量夠用
   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {//如果elementData是空數組
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//最小容量是size+1或預設容量(10)中的最大值
        }
        ensureExplicitCapacity(minCapacity);//[3]
    }   
           

ensureExplicitCapacity方法中,當發現要存儲元素的容量(size+1)大于elementData的長度時,則調用grow方法對數組擴容。

[3]處調用ensureExplicitCapacity(minCapacity)方法  
   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//如果指定的容量超出了elementData數組的現有容量,要擴容
            grow(minCapacity);//[4]
    }        
           

千呼萬喚始出來,grow方法設定數組新容量為舊的數組容量+舊的數組容量右移一位。和size+1(這是能容納add進來的元素的最小容量minCapacity)比較,如果小于,則使用minCapacity。如果newCapacity超出了MAX_ARRAY_SIZE,還需要調用hugeCapacity方法進一步處理。

[4]處調用了grow(minCapcity)實作擴容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量為舊的數組容量+舊的數組容量右移一位
        if (newCapacity - minCapacity < 0)//和minCapacity比較,至少也要能容納新添加進來的元素
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//[5]
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//數組擴容
    }  
           

當容量超出Integer.MAX_VALUE時(即minCapacity<0),記憶體溢出。

[5]處調用hugeCapacity(minCapacity)
   private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow.容量超出Integer最大值(即為負數),記憶體溢出
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?//最大容量為Integer.MAX_VALUE或 MAX_ARRAY_SIZE
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    } 
           

通過以上幾個繁複的方法後,終于可以保證add方法容量夠用了,此時add方法在elementData下标size+1處出入元素e即可。

(5)addAll方法

boolean addAll(Collection<? extends E> c):将集合c中的所有元素添加到elementData的末尾。

将要添加的集合調用toArray()轉換為數組,并得到數組長度numNew.然後確定elementData的容量(依然調用ensureCapacityInternal方法)足夠添加新的集合.。

将要添加的集合中的所有元素複制到elementData從size位置往後的位置,修改size.

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;//如果要添加的元素中沒有元素,傳回false
    }
           

boolean addAll(int index,Collection<? extends E>)重載的方法。從elementData指定下标位置開始添加集合c中的元素。判斷需要移動元素的數量。再調用arraycopy具體複制。

public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);//下标檢查

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  //確定容量
		//從index及其以後的所有元素都需要移動,以便插入新的集合元素
        int numMoved = size - index;
        //如果需要移動,将index及其以後的所有元素移動到從index+numNew及其以後的位置處
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
		//将要添加的集合複制進來
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

           

看完所有集合關于增add,addAll的操作。總結起來,涉及向ArrayList中添加元素之前,要先調用ensureCapacityInternal方法確定内部容量。然後可能會出現接下來的方法調用。

ensureCapacityInternal->ensureExplicitCapacity->grow->hugeCapacity,均是為了保證配置設定合适的容量用來添加元素。如果涉及根據指定下标添加元素時,需要調用rangeCheckForAdd方法來保證下标的正确性。緊接着,就是向elementData數組中添加元素的相關操作了。如果添加一個元素,直接在相應位置添加。如果添加多個元素,使用System.arraycopy方法或Arrays.copyof方法等進行操作。

(6)看完增(add),接下來再看删(remove)的相關方法。

E remove(int index):list中獨有的根據指定下标來移除元素的方法。

先調用rangeCheck方法確定要移除元素的下标的正确性。由于要删除該index下标處的元素,是以index往後的所有元素響應的要向前移動一位。使用System.arraycopy方法,将elementData從index+1到最後的所有元素複制到index開始往後。最後将之前elementData中size-1位置的元素(list中的最後一個元素)置為null。傳回移除的值。

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

    modCount++;
    E oldValue = elementData(index);
	//需要移動的元素的數量
    int numMoved = size - index - 1;
    if (numMoved > 0)//如果數量大于0,移動
    	//将從index+1以及之後的所有元素向前移動
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;//傳回值為移除的值
           

rangeCheck方法如下

調用rangeCheck方法,檢查下标,若下标越界,抛異常    
 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }   
           

boolean remove(Object o):該方法在集合中移除指定的元素。在周遊集合過程中,如果找到指定元素,則調用fastRemove方法移除該元素。

remove(Object o):該方法根據指定的元素在集合中移除該元素
  public boolean remove(Object o) {
  		//如果要移除的元素為null,周遊集合找到為null的元素移除
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);//[8]
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
           

調用fastRemove來移除元素,該方法和remove(int index)方法相比,就少調用了rangeCheck(index)而已,

因為上面的remove(Object 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
    }    
           

clear方法相比較于remove一個個的移除元素而言,clear方法則是清空集合中的所有元素。

clear
将集合中所有元素的引用設定為null,讓垃圾回收器去解決吧.别忘了把size設定為0,代表現在list中沒有元素
 public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }
           

(7)看完了增(add,addAll)删(remove,clear)操作,再來看看改(set)相關的方法。

set(int index,E element):該方法用于在指定下标處替換原來的元素,傳回原來的元素。涉及下标相關的都要進行rangeCheck操作。

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

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

(8)增删改都介紹完了,下面介紹查(get)相關的方法。

get(int index)

get:該方法根據指定下标傳回集合中的元素
 public E get(int index) {
        rangeCheck(index);//[7]
        return elementData(index);
    }
           

indexOf():該方法傳回集合中要查詢元素第一次出現的下标。

public int indexOf(Object o) {
    if (o == null) {//如果要查詢的元素為null
        for (int i = 0; i < size; i++)
        //直接找到elementData中元素為null的下标并傳回即可
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
        //周遊集合,如果equals,則傳回下标
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;//查無,則傳回-1
}   
           

boolean contains(Object o):

public boolean contains(Object o) {
 //由于indexOf方法在查詢不到指定元素時傳回-1,查詢到時傳回下标,是以通過>=0即可判斷是否包含指定元素o
        return indexOf(o) >= 0;
    }
           

int lastIndexOf(Object 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;
}   
           

(9)toArray方法:該方法将elementData從下标0開始size長度的元素(即所有存儲在集合中的元素)複制并傳回。

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
           

T[] toArray(T[] a)重載的方法,需要指定一個類型的數組,并傳回該類型的數組。如果指定的數組大小小于size,則傳回一個包含ArrayList中所有元素的新數組,否則複制并将size下标處的元素設定為null

public <T> T[] toArray(T[] 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;//a數組size下标處置為null
        return a;
    }
           

舉個例子。

@Test
	public void testToArray(){
		List<Integer> list=new ArrayList<Integer>();
		for(int i=0;i<5;i++){
			list.add(i);
		}
		Integer[] arr=list.toArray(new Integer[]{11,22,33,44,55,66,77,88});
		System.out.println(Arrays.toString(arr));//[0, 1, 2, 3, 4, null, 77, 88]
	}
           

就說到這裡,寫了好幾個小時,準備了好久,有不足之處和錯誤之處還望指正。