[集合類]源碼解析5(ArrayList類、Vector類和Stack類)

上一篇:[集合類]源碼解析4(List接口和AbstractList抽象類)

1. 概述

前面我們按照接口爲線索,分析了和集合類相關的一些接口以及他們的實現類,下面我們將針對具體的類來分析其具體實現。我們將瞭解到容器的存儲實現、構造方法、常用方法。

Vector的實現和ArrayList基本相同,二者的區別是,Vector是線程安全的,其寫方法都是syncronized修飾的。

Stack是棧的實現,繼承了Vector,所以也是線程安全的。Stack的JavaDoc中有這樣一段話:

在這裏插入圖片描述

// 翻譯參考:
// Deque接口及其實現提供了一組更完整、更一致的後進先出堆棧操作,應該優先使用這些操作。例如:
// Deque<Integer> stack = new ArrayDeque<Integer>();

當然,我們在Deque的文檔中也找到了相關說明:

在這裏插入圖片描述

// 翻譯參考:
// Deques也可以作爲後進先出的堆疊。這個接口應該優先用於遺留堆棧類。
// 當deque用作堆棧時,元素從deque的開頭被推入和彈出。
// 如下表所示,堆棧方法與Deque方法完全等價:

所以,當沒有併發要求時,我們應該優先使用Deque實現棧。

2. ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

RandomAccess, Cloneable, Serializable接口我們在前面已經分析過了,傳送門: [集合類] 源碼解析1(Iterable、RandomAccess、Serializable、Cloneable)

迭代器也分析過了,傳送門:[集合類] 源碼解析2(Iterator的實現)

List接口和AbstractList抽象類也分析過了,傳送門:[集合類]源碼解析4(List接口和AbstractList抽象類)

1)屬性

private static final long serialVersionUID = 8683452581122892189L;

// 默認初始化大小
private static final int DEFAULT_CAPACITY = 10;

// 空數組,供空實例使用
private static final Object[] EMPTY_ELEMENTDATA = {};

// 與EMPTY_ELEMENTDATA區分開,以瞭解添加第一個元素時的膨脹程度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存儲元素的數組。ArrayList容量是這個數組的長度。
// elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList在添加第一個元素時會擴充爲DEFAULT_CAPACITY
transient Object[] elementData; // non-private to simplify nested class access

// ArrayList的size,包含的元素數量
private int size;

可以看到elementData被transient關鍵字修飾了,這個關鍵字的意思是不會被序列化,誒?但是ArrayList還實現了Serializable接口,這是怎麼回事?

由於ArrayList是動態擴容的,所以數組中可能存在沒有存儲元素的單元,如果採用外部的序列化實現的話,就會序列化整個數組,浪費時間和空間。我們後面會看到 ArrayList 有 writeObject 和 readObject 兩個方法,實際上就是序列化和反序列化的方法。

2)構造方法

ArrayList有三個構造方法,第一個傳入初始大小,當 ArrayList 新增元素時,如果空間不足,會進行動態擴容,會導致整個數組進行一次內存複製。因此,在初始化 ArrayList 時,可以預估數組大小,這樣可以減少擴容次數,提高系統性能。第二個是空參構造,數組默認引用DEFAULTCAPACITY_EMPTY_ELEMENTDATA。第三個傳入集合,通過toArray方法轉化爲數組,引用賦給數組,如果集合長度爲0,數組引用EMPTY_ELEMENTDATA。

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

public ArrayList() {
  	// 默認空數組,和EMPTY_ELEMENTDATA區分
  	// 在3)常用方法(5)關鍵方法中還會提到
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

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 {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3)常用方法

(1)添加方法

擴容請查看(5)關鍵方法,我們之前迭代器文章我們提到modCount是避免進行併發修改的,按理說在添加方法中應該modCount++,但是我們並沒有看到相關語句,將在(5)關鍵方法中講到。

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

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

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)
      	// index後面元素向後移動numNew距離
    		System.arraycopy(elementData, index, elementData, index + numNew,
                     numMoved);
		// 添加元素
  	System.arraycopy(a, 0, elementData, index, numNew);
  	size += numNew;
  	return numNew != 0;
}

(2)刪除方法

public E remove(int index) {
  	// 保證index合法
    rangeCheck(index);

    modCount++;
  	// 返回舊值
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
      	// 將index後面元素前移一個單元
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
  	// 最後一個空間置空
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

// 遍歷,調用fastRemove方法
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;
}

// 相比remove空參方法,省去了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
}

// 刪除c中存在的元素
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}
// 刪除c中不存在的
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

// 上面兩個方法都通過該方法實現
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++)
          	// complement爲false,保存c中不存在的元素
          	// complement爲true,保存c中存在的元素
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
      	// 存在併發修改,
        if (r != size) {
          	// 如果存在併發刪除,則size - r爲負數
          	// System.arraycopy文檔中有說明,會拋出IndexOutOfBoundsException運行時異常
          	// 如果存在併發添加,則將添加的元素追加到w索引後面。
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - 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;
            modified = true;
        }
    }
    return modified;
}


public void clear() {
    modCount++;

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

    size = 0;
}

(3)修改方法

// 修改index索引的值,返回舊值
public E set(int index, E element) {
    rangeCheck(index);

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

(4)查詢方法

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

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

    return elementData(index);
}

// 這種處理方法我們見過很多次了,針對o是否爲空,分別處理
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
// 參考indexOf,區別在於遍歷是倒序的
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)關鍵方法

在添加方法中都調用了ensureCapacityInternal來保證數組空間大小,下面我們分析一下。

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 首先判斷elementData數組是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// 也就是調用空參的構造方法,第一次添加元素的時候
// 如果是的話,返回Math.max(默認數組大小10, 參數minCapacity)
// 否則返回參數minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
  	// 原來modCount在這裏+1,難怪在添加方法中找不到
    modCount++;

    // overflow-conscious code
  	// 如果空間不夠了,就要擴容
    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
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
  	// 如果超過了最大數組大小,根據參數minCapacity的大小需要,進行設置
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
  	// elementData引用到新數組,舊數組等待GC,所以我們要儘量避免擴容操作
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

(6)其他方法

// 縮減數組大小到元素數量
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

// 重寫了AbstractCollection的方法,效率更高
public Object[] toArray() {
  	// 返回副本
    return Arrays.copyOf(elementData, size);
}

public <T> T[] toArray(T[] a) {
  	// 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());
  	// a的空間足夠,將elementData複製給a,返回a
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

4)System.arraycopy

通過上面的源碼閱讀,我們可以發現,ArrayList由於使用數組存儲,很多地方都使用了System.arraycopy方法,(Arrays.copyOf方法內部也是使用了System.arraycopy)。由於System.arraycopy是本地方法,下面我們閱讀一下他的文檔。

由於博主英文水平有限,爲避免歧義,先給出英文文檔,有能力的同學可以自己閱讀。下面將根據我的理解進行翻譯,有錯誤的地方,望不吝賜教。

在這裏插入圖片描述

從指定的src數組(srcPos索引開始)複製到dest數組的指定位置(destPos索引)。數組的子序列從src引用的源數組複製到dest引用的目標數組,複製的元素數量等於length參數。元素從源數組的srcPos到srcPos + length - 1位置按順序複製到目標數組的destPos 到 destPos + length - 1位置。

如果src和dest參數引用的是同一個數組對象,就好像首先複製srcPos到srcPos + length - 1位置的元素到一個容量爲length的臨時數組,然後臨時數組的內容再複製到destPos 到 destPos + length - 1位置的目標數組。

如果 dest 爲空,拋出 NullPointerException 異常

如果 src 爲空, 拋出 NullPointerException 異常,並且不修改目標數組

另外,如果下列任何情況成立,則拋出ArrayStoreException,並且不修改目標數組:

src參數引用的對象不是數組。
dest參數引用的對象不是數組。
src參數和dest參數引用的是元素類型不同的原始類型的數組。
src參數引用的是一個具有基本元素類型元素的數組,dest參數引用的是一個具有包裝類型元素的數組。
src參數引用一個具有包裝類型元素的數組,dest參數引用一個具有基本類型元素的數組。

另外,如果下列任何情況成立,則拋出IndexOutOfBoundsException,並且不修改目標數組:

srcPos參數是負的。
destPos參數是負的。
length參數是負的。
srcPos+length大於src.length,即源數組的長度。
destPos+length大於destination.length,即目標數組的長度。

另外,如果從srcPos到srcPos+length-1位置的源數組的任一元素不能通過賦值轉換爲目標數組元素的類型,則拋出ArrayStoreException。在這種情況下,令k爲小於length的最小非負整數,如果src[srcPos+k]不能轉換爲目標數組元素的類型,當拋出異常時,位置srcPos到srcPos+k-1的源數組元素已經複製到目標數組destPos到destPos+k-1位置,目標數組的其他位置將不會被修改。(由於已經詳細列出了這些限制,本段實際上只適用於兩個數組都具有引用類型元素的情況。)

3. Vector

通過查看源碼我們可以發現,Vector方法和ArrayList基本相同,不過在修改方法上,都使用synchronized修飾。Vector是ArrayList的同步版本,所以這裏就不再贅述了。

4. Stack

開頭我們已經說過,官方文檔推薦用Deque實現棧,這裏我們只是簡單看一下Stack類的源碼。

// 短短不到50行,非常簡單,對列表操作進行限制,就是棧了。
// 需要注意的是 三個同步方法的synchronized是爲了保證size()獲取的共享變量的可見性
public class Stack<E> extends Vector<E> {

    public Stack() {
    }

    public E push(E item) {
        addElement(item);

        return item;
    }
		
    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

    public boolean empty() {
        return size() == 0;
    }

    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

    private static final long serialVersionUID = 1224463164541339165L;
}

下一篇:[集合類]源碼解析6(Queue接口、AbstractQueue抽象類、Deque接口)

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