ArrayList源碼分析(JDK1.8 個人理解)

ArrayList 源碼手記

​ 從大學到現在ArrayList源碼看了忘忘了看,今天有空記錄下自己看的東西以加深印象。下面開始按照自己的想法開始分析ArrayList源碼。

​ 1、先記錄下ArrayList的類繼承關係圖

在這裏插入圖片描述

​ 2、下面記錄下ArrayList所實現接口中的三個標籤接口,大部分人只熟悉第一個標籤接口

//先看下ArrayList的定義形式,實現了四個接口,其中後三個爲標籤接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, 	Cloneable, java.io.Serializable
{
}
// 標籤接口一,大家最熟悉的序列化接口
public interface Serializable {
}
// 標籤接口二,隨機訪問接口,暗示這個類支持快速(通常是常量時間)隨機訪問
public interface RandomAccess {
}
// 標籤接口三,克隆接口
public interface Cloneable {
}

​ 3、再來看下ArrayList的屬性有哪些

	// 默認初始化容量,若是無參構造的ArrayList,在添加第一個元素時候會將容量膨脹到下面大小
    private static final int DEFAULT_CAPACITY = 10;
	// 這個元素數組是靜態的,那麼全局唯一資源,在指定容量爲0時或者其他幾個場景會讓elementData賦值爲此數組
    private static final Object[] EMPTY_ELEMENTDATA = {};
	// 這個數組按照我個人理解,類似一個標記數組,當無參化構造了ArrayList後,會將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在調用ArrayList的add方法添加第一個元素時候會判斷element是不是和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一引用,若是,就嘗試將數組快速膨脹到DEFAULT_CAPACITY容量大小
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	// 這個是實際存儲元素的數組
    transient Object[] elementData; // non-private to simplify nested class access
	// 這個字段是實際元素的個數
    private int size;
	// 最大數組大小是2147483647 - 8 個 
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

​ 4、接下來看看ArrayList的構造函數們

// 構造方法一,無參構造方法,將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    	//爲什麼只有在無參構造函數時纔將elementData賦值爲DEFAULTCAPACITY_EMPTY_ELEMENTDATA還暫未理解,猜想可能是帶參構造函數裏面的容量不能隨意擴容,以防改變使用者原意。
}

// 構造方法二,帶指定容量參數的構造方法
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 在這裏若容量指定爲0的時候,把elementData賦值爲EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
 }

// 構造函數三,帶集合參數的構造方法
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 在這裏若集合參數內容爲空,則讓elementData也指向EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
 }

​ 5、ArrayList 函數分析,先從常用的函數分析

​ 5.1 add(E e)

// 先分析add方法,add方法將給定元素添加到elementData末尾,返回值爲boolean類型
public boolean add(E e) {
    	// 第一步先驗證容量和統計修改次數
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 末尾添加給定值,size自增1
    	elementData[size++] = e;
        return true;
}

private void ensureCapacityInternal(int minCapacity) {
    	// 這裏有兩個函數調用,下面逐一分析
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	// 在這一步可以看出,若elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是同一引用,那麼在添加第一個元素時候minCapacity就是1,肯定返回 DEFAULT_CAPACITY
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
        // 修改次數加1
    	modCount++;
        // 當minCapacity大於實際數組長度時候擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}

// 擴容代碼
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
    	// 新容量爲實際元素長度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
    	// 如果新的容量還小於minCapacity,那就讓新容量等於minCapacity,如果minCapacity值溢出上界,那麼下面這個判斷就不會執行。還有好幾種情況如兩個都溢出等等。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 最後將調用Arrays.copy將生成一個新容量大小的數組,並將原來數組值放入,底層調用System.copy(src,srcPos,des,desPos,length)方法
        elementData = Arrays.copyOf(elementData, newCapacity);
}

​ 5.2 add(int index, E element)

// 在指定索引位置添加元素
public void add(int index, E element) {
        rangeCheckForAdd(index);
		// 容量檢查,在上面已經分析過
        ensureCapacityInternal(size + 1);  // Increments modCount!!
    	// 這段就不多說了,將index位置及以後所有元素統一向後挪一個位置
        System.arraycopy(elementData, index, elementData, index + 1,size - index);
        elementData[index] = element;
        size++;
}

// add方法的範圍檢查
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

​ 5.3 addAll(Collection<? extends E> c) 、addAll(int index, Collection<? extends E> c)

// 將一個集合所有元素添加到ArrayList中
public boolean addAll(Collection<? extends E> c) {
    	// 調用collection的通用方法toArray將集合轉爲數組
        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;
}

​ 5.4 get(int index)

// 第二個比較常用的方法就是獲取元素的方法get
public E get(int index) {
    	// 先做索引範圍檢查
        rangeCheck(index);
    	// 然後返回索引位置元素
        return elementData(index);
}
private void rangeCheck(int index) {
    	// 如果索引大於實際數組長度就拋異常
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
        return (E) elementData[index];
}

​ 5.5 contains(Object o)

// 個人感覺第三常用的方法就是用contains判斷一個元素是否存在數組中
public boolean contains(Object o) {
        return indexOf(o) >= 0;
}

//這個方法是找元素在數組中首次出現的索引,被contains使用
public int indexOf(Object o) {
    	// 如果給定值爲null,那麼遍歷數組元素看哪個元素的值爲null
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            // 若給定值 != null 遍歷數組逐個對比
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
    	// 沒找到返回-1
        return -1;
}

// 這個是查找元素最後一次出現的索引值,這個就是從數組最後面的元素向前遍歷
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;
 }

​ 5.6 remove(int index)

// 下來比較常用的方法就是remove方法,移除指定位置的元素
public E remove(int index) {
    	// index範圍檢查
        rangeCheck(index);
    	// 修改次數自增一
        modCount++;
    	// 獲取要移除索引位置的元素
        E oldValue = elementData(index);
		// 總共要移動的元素的數量
        int numMoved = size - index - 1;
    	// 如果要移動的數量大於0,那麼index之後的所有元素向前移一個位置
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // clear to let GC do its work
		// 返回要移除的元素
        return oldValue;
}

// 這個方法是移除指定元素,因爲要判斷數組裏面是否存在指定元素,所以算法複雜度O(n)
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    // 先找到指定元素的索引,如果找到索引位置了,那麼移除時候就不需要索引範圍檢查,所以叫fastRemove
                    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++;
        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
}

​ 以上就是ArrayList的主要方法,在ArrayList內部還有一些不常用的方法和內部類。現在可以看出ArrayList內部基於一個對象數組存放元素,因此擁有數組的特性: 定位快,但是查找需要O(n)複雜度,刪除需要挪動的元素較多。

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