Java源碼篇之容器類——ArrayList

1、前言

對於平常開發的時候遇到的ArrayList,在此做一個簡單的源碼閱讀記錄,JDK1.8版本。

2、ArrayList的類關係

在這裏插入圖片描述

首先看下ArrayList的類關係圖,可以看到實現了

  • Serializable接口,支持序列化與反序列化;

  • Cloneable接口,可以被克隆;

  • RandomAccess接口,支持隨機訪問,另外fori循環會比迭代器循環效率高,代碼如下:

    for (int i=0, n=list.size(); i < n; i++)
             list.get(i); 
    

    比這個循環運行得更快:

    for (Iterator i=list.iterator(); i.hasNext(); )
             i.next(); 
    

3、ArrayList的源碼

一、類的屬性

/**
 * 默認初始容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * (用於空實例的)共享空數組實例
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * (用於默認大小的空實例的)共享空數組實例,我們將其與空的元素數據區分開來,
 * 以瞭解何時添加第一個元素.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 存儲ArrayList元素的數組緩衝區。ArrayList的容量是此數組緩衝區的長度。任何
 * elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA elementData 
 * 將在添加第一個元素時擴展到默認容量(10)。
 */
transient Object[] elementData;		// transient關鍵字標記的成員變量不參與序列化過程

/**
 * ArrayList的大小(包含的元素數)
 */
private int size;

/**
 * 可分配的數組的最大大小,可能會導致OutOfMemory錯誤:Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

二、add()方法

// 默認往數組末尾添加元素
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 大小加1
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {		// minCapacity = size + 1
    // elementData 是成員變量
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {/ 如果
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		// 數組爲空時,返回較大的數
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity; // 數組非空時,返回 minCapacity = size + 1
}

private void ensureExplicitCapacity(int minCapacity) {		// minCapacity = size + 1
    modCount++;		// 計數
    // 數組元素個數加1之後如果大於當前數組長度,則進行擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {						// minCapacity = size + 1
    int oldCapacity = elementData.length;					// 舊數組的長度
    int newCapacity = oldCapacity + (oldCapacity >> 1);		// 新數組的長度 = 1.5倍舊數組長度
    // 新數組長度小於數組元素個數加1
    if (newCapacity - minCapacity < 0)						
        newCapacity = minCapacity;
    // 新數組長度大於數組最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 創建一個新的數組,並把舊數組元素複製過去,newCapacity爲新數組大小
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)		
        // 新數組長度大於數組最大值,並且minCapacity<0纔會拋出oom錯誤
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

// 指定位置添加元素
public void add(int index, E element) {
    // 下標合法性校驗
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1); 
    // 生成一個index位置爲null的新數組
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

三、get()方法

public E get(int index) {
    // 檢查合法性
    rangeCheck(index);	
    // 返回數組元素
    return elementData(index);
}

private void rangeCheck(int index) {
    // 下標位置大於等於數組長度的時候(數組下標從0開始),拋出下標越界異常
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}

四、remove()方法

// 刪除指定下標的元素
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;
}

// 刪除指定的元素,第一個出現的
public boolean remove(Object o) {
    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++;
    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
}

五、set()方法

// 將index位置的元素置換成element
public E set(int index, E element) {
    rangeCheck(index);

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

4、總結

  • 通過閱讀源碼可以看出,ArrayList的這些方法都沒有加鎖,所以在多線程環境下是不安全的;
  • add()和remove()方法都需要創建新的數組,是比較消耗性能的;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章