1. 程式人生 > >JDK集合原始碼分析(一)ArrayList

JDK集合原始碼分析(一)ArrayList

1.ArrayList簡介

1)對陣列的封裝。可以動態增長和縮減。是一種基於陣列實現的List類。是一種順序表。 2)ArrayList和Vector比較。Vector是一個較老的集合,具有很多缺點,不建議使用。ArrayList是執行緒不安全的,當多條執行緒訪問同一個ArrayList集合時,程式需要手動保證該集合的同步性,而Vector則是執行緒安全的。 3)ArrayList的類圖 (圖片來自百度相簿) 在這裡插入圖片描述

2.類繼承結構:

在這裡插入圖片描述

3.類中的屬性

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

    //預設容量
    private static final int DEFAULT_CAPACITY = 10;

    //空物件陣列  會被多次賦值使用
    private static final Object[] EMPTY_ELEMENTDATA = {};

   //預設的空物件陣列 
   //預設使用的初始化陣列 和上面區別在於當第一個元素被加入進來的時候它知道如何擴張在add(E e)中第一行體現
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 

    //元素陣列
    transient Object[] elementData; // non-private to simplify nested class access

    //實際元素大小,預設為0
    private int size;

4.構造方法

ArrayList有三個構造方法: 在這裡插入圖片描述

4.1無參構造方法

/**
	 * ArrayList三個構造方法
	 * 作用就是初始化核心陣列 elementData
	 */
	 
	 //建構函式三者之一:無參構造()
	//預設容量10,實際Object陣列賦值一個預設的空物件陣列
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

4.2有參構造方法——指定大小

 //建構函式 三者之二:指定初始大小(int)
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {  
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {  	//如果自定義大小小於0,報非法資料異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

4.3有參構造方法——指定大小

    //建構函式三者之三:構造(Collection) 不常用
	//用法:將Collection<Student> 轉換為 source.ArrayList<Person>
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();  //先轉換為陣列
        if ((size = elementData.length) != 0) {  //陣列元素不為0
            if (elementData.getClass() != Object[].class) //每個集合的toarray()的實現方法不一樣 所以判斷若返回不是Object[]型別,則用Arrays轉換成Object型別
                elementData = Arrays.copyOf(elementData, size, Object[].class);  //拷貝成Object型別的陣列
        } else {
            //陣列個數為0,則賦值空物件陣列
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

粘上copyOf的實現: 在這裡插入圖片描述

5.主要方法

5.1 add()方法(有4個)

在這裡插入圖片描述 在這裡插入圖片描述

1)boolean add(E);//預設直接在末尾新增元素

 // add方法四之一:(E)
    public boolean add(E e) {
        ensureCapacityInternal(size + 1); //判斷新增一個後容量變成size+1的這個個數 陣列能否放得下 和陣列.length比較 // Increments modCount!!
        elementData[size++] = e;  //新增在陣列末尾
        return true;
    }

2)void add(int,E);在特定位置插入元素

 // add方法四之二:在特定位置插入元素
    public void add(int index, E element) {
        rangeCheckForAdd(index);  //不在0~size之內的,就丟擲IndexOutOfBoundsException

        ensureCapacityInternal(size + 1);  //確保容得下插入的元素// Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); //從index開始所有的元素都後移一格
        elementData[index] = element;//然後把要插入的元素放到index位置
        size++;
    }
 //確保minCapacity不會超出現在的陣列大小
    private void ensureCapacityInternal(int minCapacity) {  //minCapacity表示當前實際size+1,放第一個元素時size(0)+1=1
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {  //假如元素陣列還是空陣列,空陣列沒法放元素,需要確定初始增加多大容量
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY=10;minCapacity是10一下全為10,10以上為minCapacity
        }

        //上面只是提升擴容的閾值(10)
		//確認實際的容量,上面只是第一次放元素時將minCapacity=10,而後來放元素相當於只是對ensureExplicitCapacity的封裝
        ensureExplicitCapacity(minCapacity);
    }
    //確保minCapacity不會超出現在的陣列大小(超過就擴容)
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;  //修改次數自增
		 /*第一種情況:由於elementData初始化時是空的陣列,那麼第一次add的時候,minCapacity=size+1;也就minCapacity=1,在上一個方法(確定內部容量ensureCapacityInternal)就會判斷出是空的陣列,就會給
		 將minCapacity=10。
		 第二種情況:elementData不是空的陣列,那麼在add的時候,minCapacity=size+1;也就是minCapacity代表著elementData中增加之後的實際資料個數,拿著它判斷elementData的length是否夠用,如果length
		  不夠用,那麼肯定要擴大容量。
		 */
        if (minCapacity - elementData.length > 0) //minCapacity如果大於了實際elementData陣列的長度,那麼就說明elementData陣列的長度不夠用,
			//arrayList能自動擴充套件大小的關鍵方法就在這裡了
            grow(minCapacity);
    }
private void grow(int minCapacity) {
        int oldCapacity = elementData.length; //擴充前的elementData大小
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
        if (newCapacity - minCapacity < 0)//如果擴充1.5倍還是不夠 那就用minCapacity 
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) //如果newCapacity超過了最大的容量限制(Integer.MAX_VALUE - 8)(這個數字可能其他地方會用到,所以有這麼一出),就呼叫hugeCapacity,也就是將能給的最大值(Integer.MAX_VALUE)給newCapacity
            newCapacity = hugeCapacity(minCapacity);
		 //新的容量大小已經確定好了,就copy陣列,改變容量大小。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3)boolean addAll(Collection<? extends E> c) 在末尾插入所有collection中的元素

//add方法四之三: 往末尾插一個Collection
    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); //從a陣列拷貝到elementData陣列
        size += numNew;
        return numNew != 0;
    }

4)boolean addAll(int index, Collection<? extends E> c) 往指定位置插一個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);  //從index開始的元素統統向後移動index+numNew

        System.arraycopy(a, 0, elementData, index, numNew);//再將a陣列放到index位置
        size += numNew;
        return numNew != 0;
    }

5.2 刪除方法

在這裡插入圖片描述 這幾個刪除方法都是類似的。我們選擇幾個看 在這裡插入圖片描述

1)remove(int):通過刪除指定位置上的元素

 //刪除方法5 之1:刪除指定位置上的元素
    public E remove(int index) {
        rangeCheck(index);  //元素大於等於size 拋異常

        modCount++;   //修改次數 加1
        E oldValue = elementData(index);  //獲取老值,以便回收

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);  //index後面的所有元素都前移一格
        elementData[--size] = null; //所有元素前移後,最後一個元素沒用了,置空,儘快回收  // clear to let GC do its work

        return oldValue;
    }

//不用檢測負的,因為如果索引為負,會在訪問陣列的時候,丟擲ArrayIndexOutOfBoundsException
 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
   

2)remove(Object):根據Object元素查詢刪除此object

 //刪除方法5 之2:根據Object元素查詢刪除此object
    public boolean remove(Object o) {
        if (o == null) {   //要刪除 null  最主要是知道arrayList可以儲存null值!!!
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {//刪掉遇到的第一個null元素
                    fastRemove(index);
                    return true;   //刪除成功返回true
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {  //用equals判斷
                    fastRemove(index); //刪除遇到的第一個o元素
                    return true;  //刪除成功返回true
                }
        }
        return false;   //刪除失敗返回false
    }

    //私有方法,僅供remove(Object)使用,相比public的remove,"快"在了: 1.沒有邊界檢查(因為在remove(Object)中已經確保了邊界) 2.沒有返回刪除掉的元素
    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; //將最後一個置為null 儘快回收// clear to let GC do its work
    }

3)removeAll(collection c)://批量刪除

 //刪除在Collection c中的所有元素
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c); //確保c非空 否則拋NullPointerException 空指標異常
        return batchRemove(c, false);  //批量刪除
    }
    // 刪除存在於Collection c 中的所有的所有元素
    //用於兩個方法,(complement為false時) removeAll():清除指定集合中的元素;(complement為true時)retainAll()用來測試兩個集合是否有交集。 
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;  //r用來控制迴圈,w是記錄有多少個交集
        boolean modified = false;  //標記陣列結構是否修改過
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement) //依次檢查集合c中是否包含本元素陣列中的所有元素
                    elementData[w++] = elementData[r];  //從0開始,收集滿足條件的元素(不包含或者包含),這塊複用了elementData。如果complement是false,就是將不包含在c中的元素挑揀出來
        } finally {
            //如果contails方法使用過程報出異常
            if (r != size) {  //沒有遍歷完,篩選中途報了異常 說明遍歷中途丟擲了異常
             //將剩下的元素都賦值給elementData
                System.arraycopy(elementData, r,//將還沒有經過遍歷篩選的元素 全部拷貝到篩選出來的元素末尾
                                 elementData, w,
                                 size - r); //一共拷貝r到size-1位置的元素
                w += size - r; //實際挑揀出來的元素數目 加上r後面沒有遍歷到的所有元素
            }
            if (w != size) {   //挑選出來的元素只是一部分,不是全部,將沒選中的元素清除出去
                // clear to let GC do its work
                for (int i = w; i < size; i++)  //將挑選出來的元素後面的元素全部置空 加快回收
                    elementData[i] = null;
                modCount += size - w;  //修改次數加上置空的元素個數
                size = w;  //this.elementData實際容量變為w(即只要挑選出來的元素)
                modified = true;
            }
        }
        return modified;
    }

附帶看下 clear()實現,它與remove不同

//將陣列中所有元素賦值為null  以便GC快速回收掉
    public void clear() {
        modCount++;

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

        size = 0;
    }

5.3 indexOf()方法

//查詢某個元素的下標,對應lastIndexOf()是從尾部開始找
    public int indexOf(Object o) {
        if (o == null) {  //要查詢的元素為 null
            for (int i = 0; i < size; i++)  //查詢第一個為null的元素位置
                if (elementData[i]==null)
                    return i;
        } else {    //要查詢的元素不為 null
            for (int i = 0; i < size; i++)   //找到第一個此元素的下標返回
                if (o.equals(elementData[i])) //用equals比較,所以得分null和非null
                    return i;
        }
        return -1;
    }

5.4 get()方法

public E get(int index) {
        rangeCheck(index); //檢驗索引合法 index >= size 拋越界異常
        return elementData(index);
    }

5.5 set()方法

//在index位置覆蓋element元素
    public E set(int index, E element) {
        rangeCheck(index); //檢測索引合法 index >= size 拋異常

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;  //返回舊值
    }

6. 總結

1)(是陣列)arrayList本質上就是一個elementData陣列。 2)(能擴充套件)arrayList區別於陣列的地方在於能夠自動擴充套件大小,其中關鍵的方法就是gorw()方法。 3)(可放null)arrayList可以存放null。 4)(可置空)arrayList中removeAll(collection c)和clear()的區別就是removeAll可以刪除批量指定的元素,而clear是將集合中的元素全部置為null,以便快速回收。 5)arrayList由於本質是陣列,所以它在資料的查詢方面會很快,而且arrayList標記了RandomAccess,所以在遍歷它的時候推薦使用for迴圈;而在插入刪除這些方面,效能下降很多,有移動很多資料才能達到應有的效果。