ArrayList源碼閱讀


前言

數組是我們最常用最簡單的數據結構,Java里對數組做了一個簡單的包裝,就是ArrayList,提供自動擴容的功能。

最常用法

list在我們日常代碼中最為常用的做法是創建一個list,放入數據,取出數據。如下:

@Test
public void testList(){
    final List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("a");

    final String one = list.get(0);
    final int size = list.size();

    Assert.assertEquals("a", one);
    Assert.assertEquals(4, size);
}

下面,將從構造函數開始讀取源碼。

構造器

第一步,構造一個list對象


/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

注釋寫的很清楚,構造一個空list,初始化容量為10. 我們來看看這個初始值。

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

默認大小的共享的空array實例。可以注意到這是一個static變量,也就是說所有的ArrayList對象共享這個變量。由此可以猜測,這是一個臨時值。

然后看我們的數據存儲對象elementData.

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

ArrayList的容量(capacity)就是這個數組的長度。
另外,注意修飾關鍵字transient, 這個不常用,用來表示這個字段不可以被序列化。我們知道,ArrayList實現了Serializable接口,為什么不允許序列化data呢?具體原因參加 http://www.cnblogs.com/woshimrf/p/java-serialize.html

add

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

先保證容量,然后插入數據,size數量+1.

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

針對空list第一次add,判斷elementData是不是默認的空對象,若是空對象,計算容量。容量的計算也很有意思。

private static final int DEFAULT_CAPACITY = 10;

第一次添加后容量就是10了,當超過10之后就肯定要擴容了。

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

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

再一次看到modCount這個變量名,和HashMap一樣,記載容量發生變化的次數。而擴容的閾值也相當簡單,只要保證當前數量+1能夠容納就好。當數組長度不夠的時候,擴容。


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

擴容擴大為1.5倍。然后新建數組,長度為新的容量,復制舊數據。由於過程中沒有加鎖,ArrayList也不是線程安全的。

Get

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

    return elementData(index);
}

實現相當簡單,就是通過數組下標讀取元素。但值得學習的是編程結構。比如,這個的范圍檢測,通過一個有意義的方法名封裝了一段代碼。清晰易懂。

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

如何使用線程安全的List

自己對變化過程加鎖,或者使用

List list = Collection.synchronizedList(new ArrayList());

CopyOnWriteArrayList是一個有趣的例子,它規避了只讀操作(如get/contains)並發的瓶頸,但是它為了做到這點,在修改操作中做了很多工作和修改可見性規則。 此外,修改操作還會鎖住整個List,因此這也是一個並發瓶頸。所以從理論上來說,CopyOnWriteArrayList並不算是一個通用的並發List。(並發編程網)

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM