【集合】ArrayList 源碼分析

概述(JDK1.8.0_162)

    ArrayList基於數組實現,是一個動態數組,其容量能自動增長,類似於C語言中的動態申請內存,動態增長內存。

    每個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它總是至少等於列表的大小。隨着向ArrayList中不斷添加元素,其容量也自動增長。自動增長會帶來數據向新數組的重新拷貝,因此,如果可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前,應用程序也可以使用ensureCapacity操作來增加ArrayList實例的容量,這可以減少遞增式再分配的數量。 注意,此實現不是同步的。如果多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。

繼承關係

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
  private static final long serialVersionUID = 8683452581122892189L;
}
  • 實現了RandomAccess接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問
  • 實現了Cloneable接口,能被克隆
  • 實現了Serializable接口,支持序列化,能夠通過序列化傳輸

主要成員變量

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

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

/**
 * 用於默認大小的空實例的共享空數組實例
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 存儲ArrayList元素的數組緩衝區
 */
transient Object[] elementData; // non-private to simplify nested class access

/**
 * ArrayList包含的元素數量
 *
 * @serial
 */
private int size;
transient

    Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。爲了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。即:被標記爲transient的屬性在對象被序列化時不會被保存。

構造函數

/**
 * 初始化時指定容量大小
 */
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);
   }
}

/**
 * 無參構造函數,默認容量爲10
 */
public ArrayL ist() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 初始化一個包含指定Collection的ArrayList
 */
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;
   }
}

元素存儲

/**
 * 用指定的元素替代此列表中指定位置上的元素,並返回以前位於該位置上的元素
 */
public E set(int index, E element) {
    rangeCheck(index);

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

/**
 * 將指定的元素添加到此列表的尾部
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/**
 * 將指定的元素插入此列表中的指定位置,如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加1)
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}
/**
 * 按照指定collection的迭代器所返回的元素順序,將該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);
     size += numNew;
     return numNew != 0;
 }

 /**
  * 從指定的位置開始,將指定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);

     System.arraycopy(a, 0, elementData, index, numNew);
     size += numNew;
     return numNew != 0;
 }

元素讀取

/**
 * 返回列表中指定位置上的元素
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

元素刪除

/**
 * 移除此列表中指定位置上的元素
 */
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) {
   // ArrayList允許存放null值
    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
}

/**
 * 移除列表中所有元素
 */
public void clear() {
    modCount++;

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

    size = 0;
}

數組擴容

/**
 * 擴容此ArrayList實例的容量,確保至少包含minCapacity指定的元素數
 */
public void ensureCapacity(int minCapacity) {
   int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
       // any size if not default element table
       ? 0
       // larger than default for default empty table. It's already
       // supposed to be at default size.
       : DEFAULT_CAPACITY;

   if (minCapacity > minExpand) {
       ensureExplicitCapacity(minCapacity);
   }
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       return Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   return minCapacity;
}

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

private void ensureExplicitCapacity(int minCapacity) {
   modCount++;

   // overflow-conscious code
   if (minCapacity - elementData.length > 0)
       grow(minCapacity);
}

/**
 * 要分配的最大數組大小,超過此大小可能會導致OutOfMemoryError:請求的數組大小超過VM限制
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
   // overflow-conscious code
   int oldCapacity = elementData.length;
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
   if (newCapacity - MAX_ARRAY_SIZE > 0)
       newCapacity = hugeCapacity(minCapacity);
   // minCapacity is usually close to size, so this is a win:
   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;
}

從上述代碼中可以看出,數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中,每次數組容量的增長大約是其原容量的1.5倍。這種操作的代價是很高的,因此在實際使用時,我們應該儘量避免數組容量的擴張。當我們可預知要保存的元素的多少時,要在構造ArrayList實例時,就指定其容量,以避免數組擴容的發生。或者根據實際需求,通過調用ensureCapacity方法來手動增加ArrayList實例的容量。

Object oldData[] = elementData;//爲什麼要用到oldData[]

乍一看來後面並沒有用到關於oldData, 這句話顯得多此一舉!但是這是一個牽涉到內存管理的類, 所以要了解內部的問題。 而且爲什麼這一句還在if的內部,這跟elementData = Arrays.copyOf(elementData, newCapacity); 這句是有關係的,下面這句Arrays.copyOf的實現時新創建了newCapacity大小的內存,然後把老的elementData放入。好像也沒有用到oldData,有什麼問題呢。問題就在於舊的內存的引用是elementData, elementData指向了新的內存塊,如果有一個局部變量oldData變量引用舊的內存塊的話,在copy的過程中就會比較安全,因爲這樣證明這塊老的內存依然有引用,分配內存的時候就不會被侵佔掉,然後copy完成後這個局部變量的生命期也過去了,然後釋放纔是安全的。不然在copy的的時候萬一新的內存或其他線程的分配內存侵佔了這塊老的內存,而copy還沒有結束,這將是個嚴重的事情。

對象數組

/**
 * 返回這個集合的對象數組
 */
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

/**
 * 返回這個集合的對象數組。
 * 如果傳入數組的長度小於size,返回一個新的數組,大小爲size,類型與傳入數組相同。
 * 如果傳入數組的長度等於size,則將elementData複製到傳入數組並返回傳入的數組。
 * 如果傳入數組的長度大於size,除了複製elementData外,還將返回數組的第size個元素置爲null。
 */
@SuppressWarnings("unchecked")
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;
    return a;
}

Fail-Fast機制

ArrayList也採用了快速失敗的機制,通過記錄modCount參數來實現。在面對併發修改的時候,迭代器很快就會完全失敗,而不是冒着在將來某個不確定時間發生任意不確定行爲的風險。

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