JDK中ArrayList的實現分析

JDK中ArrayList的實現分析

第一次使用Markdown寫博客,使用MarkDownPad 2先寫好拷貝過來的。

查看Android中的JDK源碼ArrayList的實現.

JDK中的List類族,參考《Java程序性能優化》3.2.1節:

這裏寫圖片描述

ArrayList繼承自AbstractList,後者是一個抽象類,又繼承自AbstractCollection,並實現了List接口。

public abstract class AbstractList<Eextends AbstractCollection<Eimplements List<E>

查看源碼註釋:

AbstractList is an abstract implementation of the List interface, optimized
for a backing store which supports random access. This implementation does
not support adding or replacing. A subclass must implement the abstract
methods get() and size(), and to create a
modifiable List it’s necessary to override the add() method that
currently throws an UnsupportedOperationException.

可以知道,AbstractList支持隨機訪問,但是並不支持添加和替換,它的子類必須實現get()和size()這兩個抽象方法來創建一個可以修改的List。另外要複寫add()方法,因爲在AbstractList中,該方法僅僅是拋出了一個UnsupportedOperationException異常。

public void add(int location, E object) {  
   throw new UnsupportedOperationException();  
}  

AbstractList裏面定義了兩個內部類SimpleListIterator和FullListIterator,前者可以從List第一個元素開始遍歷,而後者繼承自前者,可以實現從指定位置開始遍歷,AbstractList裏面的訪問都是通過這兩個迭代器實現的。

回頭再來看ArrayList的實現。
先看註釋:

ArrayList is an implementation of List, backed by an array.
All optional operations including adding, removing, and replacing elements are supported.
All elements are permitted, including null.
This class is a good choice as your default List implementation.
Vector synchronizes all operations, but not necessarily in a way that’s meaningful to your application: synchronizing each call to get, for example, is not equivalent to synchronizing the list and iterating over it (which is probably what you intended).
java.util.concurrent.CopyOnWriteArrayList is intended for the special case of very high
concurrency, frequent traversals, and very rare mutations.

ArrayList是List的一個基於數組的實現。它支持添加,刪除以及替換原始這些所有的操作。包括null在內的所有元素都是允許的。
這個類是你實現List的一個好的選擇。Vector類對所有操作進行了同步化,但是可能在你的程序中不是十分必要:比如同步化每一次get的調用,不等於同步化整個list以及它的遍歷(這纔可能是你希望的)。
java.util.concurrent.CopyOnWriteArrayList用在高併發,經常遍歷以及很少變化的情況下。

可以知道Vector和CopyOnWriteArrayList是兩個線程安全的List的實現,而ArrayList不是線程安全的。併發List的講解可以參考Java程序性能優化一書的4.3節“JDK併發數據結構”中的“併發List”小節。

又跑題了。
ArrayList支持隨機訪問,並使用數組實現,其中定義一個容量增長的最小值:

private static final int MIN_CAPACITY_INCREMENT = 12;

這個值是時間和空間上的一個權衡。這就表示雖然基於數組實現,但是容量並不一定要指定,必要時候容量可以自動擴充。

毫無疑問,類裏面會定義一個數組:

transient Object[] array;

這個數組用於存儲元素,元素值當然可以爲null,另外也支持泛型,可以存儲不同類型的元素。

如果我們指定了構造函數中的容量,array就會根據容量進行初始化,當然如果容量小於0就會拋出IllegalArgumentException異常:

array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);

EmptyArray.OBJECT是定義的一個容量爲0的對象。

ArrarList還實現了其他的構造函數:

public ArrayList(Collection<? extends E> collection) 

該函數可以從一個集合中進行初始化,拷貝它的元素。

我們在ArrayList中添加元素的時候,調用add()方法,下面看看是怎麼實現的。

@Override public boolean add(E object) {
    Object[] a = array;
    int s = size;
    if (s == a.length) {
        Object[] newArray = new Object[s +
                (s < (MIN_CAPACITY_INCREMENT / 2) ?
                 MIN_CAPACITY_INCREMENT : s >> 1)];
        System.arraycopy(a, 0, newArray, 0, s);
        array = a = newArray;
    }
    a[s] = object;
    size = s + 1;
    modCount++;
    return true;
}

當要添加元素object的時候,首先定義array的一個引用,判斷一下元素個數是否等於數組的長度,即元素是否已經填滿了數組,如果填滿了,就沒有多餘的空間存儲新的元素,所以此時要進行擴容。
擴容算法是這樣的:
如果元素個數比MIN_CAPACITY_INCREMENT / 2小,就擴充MIN_CAPACITY_INCREMENT大小的空間,反之擴充元素個數一半的空間,用新的大小定義一個新的數組,將舊的數組的元素拷貝進去,然後將array的引用轉移到新的數組,最後更新元素個數等信息。

如果要在指定位置添加元素,實現方式就不一樣了。

@Override public void add(int index, E object) {
    Object[] a = array;
    int s = size;
    if (index > s || index < 0) {
        throwIndexOutOfBoundsException(index, s);
    }

    if (s < a.length) {
        System.arraycopy(a, index, a, index + 1, s - index);
    } else {
        // assert s == a.length;
        Object[] newArray = new Object[newCapacity(s)];
        System.arraycopy(a, 0, newArray, 0, index);
        System.arraycopy(a, index, newArray, index + 1, s - index);
        array = a = newArray;
    }
    a[index] = object;
    size = s + 1;
    modCount++;
}

首先判斷位置是否超出邊界,超出就拋出throwIndexOutOfBoundsException異常。然後如果數組空間足夠,將插入位置以後的元素整體向後挪動一個單位,否則先用前面的算法擴容,然後整體挪動。
數組整體搬移一個單位的操作還是比較消耗性能的,尤其是大量存在插入操作的時候,這個時候可以考慮性能更好的LinkedListArray,它基於鏈表,我們下次再分析它的實現。

當要remove一個位置的元素時,源碼是這樣的:

@Override public E remove(int index) {
    Object[] a = array;
    int s = size;
    if (index >= s) {
        throwIndexOutOfBoundsException(index, s);
    }
    @SuppressWarnings("unchecked") E result = (E) a[index];
    System.arraycopy(a, index + 1, a, index, --s - index);
    a[s] = null;  // Prevent memory leak
    size = s;
    modCount++;
    return result;
}

很簡單,直接將指定位置元素的數組整體前移一個單位,這樣就將要刪除的元素覆蓋掉了。

如果要remove某個對象元素時,remove(Object object)函數需要先判斷object是否爲null,再分爲兩種情況處理。如果不是null,就尋找滿足object.equals(a[i])的位置,進行整體前移的操作;如果爲null,則直接查找第一個爲a[i] == null的地方,整體前移。

ArrayList其他的操作包括contains()、indexOf()等都比較好理解,就是簡單的數組遍歷和比較,就不再贅述了。

到此爲止,我們基本上了解了ArrayList的實現。

發佈了204 篇原創文章 · 獲贊 280 · 訪問量 85萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章