1. 程式人生 > >ArrayList源碼分析

ArrayList源碼分析

覆蓋 heap one from 數組長度 -c rep amp 技術

ArrayList的聲明

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

泛型聲明,繼承於AbstractList,實現了若幹個接口。

AbstractList是List的虛基類不多說,List接口是Colloection的子接口。

RandomAccess是List所實現的標記接口,用來表明其支持快速(通常是固定時間)隨機訪問。

隨機訪問我的理解就是通過索引(index)進行訪問。

Cloneable也是標記接口,表示可以合法調用clone()方法而不拋出異常,clone()也會正常執行,復制所有自斷。

Serializable表示可以被序列化和反序列化,在io中用來傳遞數據有用。

ArrayList的域

技術分享 技術分享
 1     private static final long serialVersionUID = 8683452581122892189L;
 2   
 3     private static final int DEFAULT_CAPACITY = 10;
 4     
 5     private static final Object[] EMPTY_ELEMENTDATA = {};
 6 
 7     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 8 
 9     transient Object[] elementData; // non-private to simplify nested class access
10 
11     private int size;
技術分享

serialVersionUID是和Serializable接口配套使用的,用來確保序列化和反序列化的正常運行。

DEFAULT_CAPACITY默認初始化容量。容量是指尚未擴充前,最大存儲數據的多少。

EMPTY_ELEMENTDATA 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA 基本都是用作初始化時賦予的空的數組,區別只是在於添加首個元素的時候進行區分,參照下面。

elementData數組用作存儲數據。

size表示已經占用了多少數據。

發現elementData是用transient修飾的,意思是不參與序列化過程,為什麽要這樣設計呢?

序列化過程中其實是調用了private void writeObject(java.io.ObjectOutputStream s)方法,

因為ArrayList中的elementData其實並不是全部都存放數據的,僅僅存放了size個數據,那如果全部用作序列化和反序列化會導致效率變低,

於是就只把實際存在的數據進行序列化就能使效率變高。

ArrayList的構造方法

技術分享
1 public ArrayList() {
2         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
3     }

無參的構造方法,將elementData設為空的數組,沒有其他附加操作。

技術分享 技術分享
 1 public ArrayList(int initialCapacity) {
 2         if (initialCapacity > 0) {
 3             this.elementData = new Object[initialCapacity];
 4         } else if (initialCapacity == 0) {
 5             this.elementData = EMPTY_ELEMENTDATA;
 6         } else {
 7             throw new IllegalArgumentException("Illegal Capacity: "+
 8                                                initialCapacity);
 9         }
10     }
技術分享

帶有一個int型參數的構造方法,設一個默認的初始化參數,構建一個以該參數長度為大小的空數組,

當設的參數不能作為數組的初始化參數時會拋出IllegalArgumentException。

技術分享 技術分享
 1 public ArrayList(Collection<? extends E> c) {
 2         elementData = c.toArray();
 3         if ((size = elementData.length) != 0) {
 4             // c.toArray might (incorrectly) not return Object[] (see 6260652)
 5             if (elementData.getClass() != Object[].class)
 6                 elementData = Arrays.copyOf(elementData, size, Object[].class);
 7         } else {
 8             // replace with empty array.
 9             this.elementData = EMPTY_ELEMENTDATA;
10         }
11     }
技術分享

帶一個Collection型參數的構造方法,將Collection內的元素用c.toArray()轉換為數組直接傳給elementData。

當發現傳入長度不為0的時候還要檢測其類型是否正確轉為Object[],因為JDK編號6260652的BUG,若沒有正確轉換,還需要調用Arrays.copyOf(elementData, size, Object[].class);來進行轉換。

當傳入的是一個空的集合的時候將elementData設為默認空數組。

ArrayList的關鍵方法

技術分享 技術分享
 1 public boolean add(E e) {
 2         ensureCapacityInternal(size + 1);  // Increments modCount!!
 3         elementData[size++] = e;
 4         return true;
 5     }
 6 
 7 public void add(int index, E element) {
 8         rangeCheckForAdd(index);
 9 
10         ensureCapacityInternal(size + 1);  // Increments modCount!!
11         System.arraycopy(elementData, index, elementData, index + 1,
12                          size - index);
13         elementData[index] = element;
14         size++;
15     }
16 
17 private void ensureCapacityInternal(int minCapacity) {
18         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
19             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
20         }
21 
22         ensureExplicitCapacity(minCapacity);
23     }
24 
25 private void ensureExplicitCapacity(int minCapacity) {
26         modCount++;
27 
28         // overflow-conscious code
29         if (minCapacity - elementData.length > 0)
30             grow(minCapacity);
31     }
32 
33 private void grow(int minCapacity) {
34         // overflow-conscious code
35         int oldCapacity = elementData.length;
36         int newCapacity = oldCapacity + (oldCapacity >> 1);
37         if (newCapacity - minCapacity < 0)
38             newCapacity = minCapacity;
39         if (newCapacity - MAX_ARRAY_SIZE > 0)
40             newCapacity = hugeCapacity(minCapacity);
41         // minCapacity is usually close to size, so this is a win:
42         elementData = Arrays.copyOf(elementData, newCapacity);
43     }
44 
45 private void rangeCheckForAdd(int index) {
46         if (index > size || index < 0)
47             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
48     }
技術分享

添加元素的方法感覺十分容易,但是ArrayList的本質是數組,

添加一個元素有可能意味著數組越界,越界的解決方法就是將原來的數組擴容,這裏層層調用了3個private方法。

首先調用ensureCapacityInternal(int minCapacity),這個方法判斷了該數組是否是用了無參的構造方法,如果是,就取默認的容量和現在要求的容量的較大值最為下個函數的參數。

ensureExplicitCapacity(minCapacity)判斷要求的容量和當前容量的大小,即判斷是否需要擴容,不需要擴容則直接添加,否則就調用擴容的核心方法。

grow(minCapacity)就是擴容的核心方法,

int newCapacity = oldCapacity + (oldCapacity >> 1);將新的容量(數組大小)設定成為舊的容量的1.5倍。

然後比較新的容量和請求的容量的大小,如果仍小於請求容量的大小,就把新容量改為請求容量的大小。

如果新的容量已經超過了ArrayList設置的最大容量大小private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

就把新的容量設置為最大的容量,最後調用Arrays.copyOf(elementData, newCapacity)把原來的數據復制到新擴容的數組中,數組長度為新的容量。

最後才是把指定的元素添加到所想要添加的位置上。

簡單的public boolean add(E e)就僅僅是在最後一個元素後面添加,沒什麽好說的。

public void add(int index, E element)的添加若是在已有數據的中間添加,則需要將後面的元素逐個後移。

JDK采取的方法是調用System.arraycopy(elementData, index, elementData, index + 1,size - index);

這是個本地(native)方法,方法的用處是將index後size-index個數據復制到index+1後。

技術分享 技術分享
 1 public boolean addAll(Collection<? extends E> c) {
 2         Object[] a = c.toArray();
 3         int numNew = a.length;
 4         ensureCapacityInternal(size + numNew);  // Increments modCount
 5         System.arraycopy(a, 0, elementData, size, numNew);
 6         size += numNew;
 7         return numNew != 0;
 8     }
 9 
10 public boolean addAll(int index, Collection<? extends E> c) {
11         rangeCheckForAdd(index);
12 
13         Object[] a = c.toArray();
14         int numNew = a.length;
15         ensureCapacityInternal(size + numNew);  // Increments modCount
16 
17         int numMoved = size - index;
18         if (numMoved > 0)
19             System.arraycopy(elementData, index, elementData, index + numNew,
20                              numMoved);
21 
22         System.arraycopy(a, 0, elementData, index, numNew);
23         size += numNew;
24         return numNew != 0;
25     }
技術分享

增加多個元素其實和增加單個元素的思路是相似的,都是先確保容量大小,

然後都是調用了System.arraycopy的方法進行添加和後移。

技術分享 技術分享
 1 public E remove(int index) {
 2         rangeCheck(index);
 3 
 4         modCount++;
 5         E oldValue = elementData(index);
 6 
 7         int numMoved = size - index - 1;
 8         if (numMoved > 0)
 9             System.arraycopy(elementData, index+1, elementData, index,
10                              numMoved);
11         elementData[--size] = null; // clear to let GC do its work
12 
13         return oldValue;
14     }
15 
16 public boolean remove(Object o) {
17         if (o == null) {
18             for (int index = 0; index < size; index++)
19                 if (elementData[index] == null) {
20                     fastRemove(index);
21                     return true;
22                 }
23         } else {
24             for (int index = 0; index < size; index++)
25                 if (o.equals(elementData[index])) {
26                     fastRemove(index);
27                     return true;
28                 }
29         }
30         return false;
31     }
32 
33 private void rangeCheck(int index) {
34         if (index >= size)
35             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
36     }
37 
38 private void fastRemove(int index) {
39         modCount++;
40         int numMoved = size - index - 1;
41         if (numMoved > 0)
42             System.arraycopy(elementData, index+1, elementData, index,
43                              numMoved);
44         elementData[--size] = null; // clear to let GC do its work
45     }
技術分享

刪除單個元素ArrayList提供了兩個方法,一個是刪除指定索引的方法,另一個則是刪除ArrayList中第一次出現的特定對象(如果存在)。

刪除指定索引的方法記錄了一個numMoved,即需要移動的元素,和add一樣是調用了System.arraycopy(elementData, index+1, elementData, index,numMoved);來將元素逐個向前移動。

最後把size減一的同時置為null留給GC來處理。

刪除指定對象的方法都先是遍歷整個ArrayList,找到對象的索引,然後調用一個fastRemove(int index)方法,

該方法和刪除指定索引的方法基本相同(除了記錄返回值和參數檢測之外),就不再贅述了。

技術分享 技術分享
1 public void clear() {
2         modCount++;
3 
4         // clear to let GC do its work
5         for (int i = 0; i < size; i++)
6             elementData[i] = null;
7 
8         size = 0;
9     }
技術分享

刪除所有元素和刪除單個元素的思路是一樣的,就是置為null,讓GC去處理。

註意到刪除元素並沒有對數組的容量進行改變。

技術分享 技術分享
 1 protected void removeRange(int fromIndex, int toIndex) {
 2         modCount++;
 3         int numMoved = size - toIndex;
 4         System.arraycopy(elementData, toIndex, elementData, fromIndex,
 5                          numMoved);
 6 
 7         // clear to let GC do its work
 8         int newSize = size - (toIndex-fromIndex);
 9         for (int i = newSize; i < size; i++) {
10             elementData[i] = null;
11         }
12         size = newSize;
13     }
技術分享

另外發現一個有趣的代碼,刪除從fromIndex到toIndex的所有元素,看上去是一個挺有用的功能,

但是令人感到奇怪的是他的修飾符居然是protected,意味著程序員無法在程序中直接調用,

其實是因為removeRange(int fromIndex, int toIndex)和sublist(int fromIndex,int toIndex).clear()方法的效果是相同的,

所以並不需要額外增加一個可以被調用的方法,那為什麽還要設計這個方法呢?

首先要知道,這個方法是從AbstractList中繼承過來的,

而在AbstractList中對此方法的說明是

此方法由此列表及其 subList 上的 clear 操作調用。重寫此方法以利用內部列表實現可以極大地 改進此列表及其 subList 上 clear 操作的性能。

技術分享
1 public void clear() {
2         removeRange(0, size());
3     }

但是在ArrayList中的實現並沒有像AbstractList中這樣實現,反而是自己另外實現了,那這個方法是不是就沒有用了呢?

並不是如此,ArrayList還在自己的內部添加了一個內部類SubList。

private class SubList extends AbstractList<E> implements RandomAccess

這個類只有在其外部ArrayList調用subList方法後才會生成一個特定的實例

技術分享
1 public List<E> subList(int fromIndex, int toIndex) {
2         subListRangeCheck(fromIndex, toIndex, size);
3         return new SubList(this, 0, fromIndex, toIndex);
4     }

對應的構造方法

技術分享 技術分享
1 SubList(AbstractList<E> parent,
2                 int offset, int fromIndex, int toIndex) {
3             this.parent = parent;
4             this.parentOffset = fromIndex;
5             this.offset = offset + fromIndex;
6             this.size = toIndex - fromIndex;
7             this.modCount = ArrayList.this.modCount;
8         }
技術分享

而他的removeRange方法

技術分享 技術分享
1 protected void removeRange(int fromIndex, int toIndex) {
2             checkForComodification();
3             parent.removeRange(parentOffset + fromIndex,
4                                parentOffset + toIndex);
5             this.modCount = parent.modCount;
6             this.size -= toIndex - fromIndex;
7         }
技術分享

現在一切揭曉了,修改ArrayList的removeRange方法同時也是在修改其內部類SubList的removeRange方法,

而內部類SubList並沒有覆蓋父類的clear方法,即和AbstractList的clear方法相同,也就和JDK的說明完全符合。

技術分享 技術分享
1 public void trimToSize() {
2         modCount++;
3         if (size < elementData.length) {
4             elementData = (size == 0)
5               ? EMPTY_ELEMENTDATA
6               : Arrays.copyOf(elementData, size);
7         }
8     }
技術分享

這個方法用作將數組的容量變為數組的元素數量,可以使ArrayList所占內存空間達到最小。

具體實現是調用了Arrays.copy方法將數據復制到一個長度為size的數組中。

ArrayList的叠代器

介紹叠代器前首先要介紹一個從AbstractList中繼承的實例域modCount,這個modCount在前面也經常出現。

JDK的解釋是

從結構上修改 此列表的次數。從結構上修改是指更改列表的大小,或者打亂列表,從而使正在進行的叠代產生錯誤的結果。

這是一個並發操作的問題,例如我在遍歷的時候刪除一個元素,是否返回這個元素是未知的,即返回的結果可能出錯也可能沒錯。

在之前的很多算法中我們也發現了這個modCount的出現,在後面的方法中,modCount經常會進行檢測,發現錯誤會拋出ConcurrentModificationException。

但這個異常的拋出是盡力而為的,因為是並發操作,這個就被稱為“快速失敗”。

下面對出現modCount的代碼就不再做解釋了。

Iterator的聲明

private class Itr implements Iterator<E>

繼承自Iterator,不必多說。

Iterator的域

技術分享
1 int cursor;       // index of next element to return
2 int lastRet = -1; // index of last element returned; -1 if no such
3 int expectedModCount = modCount;

cursor表示光標,用來指示下一個元素的索引。

lastRet表示上一個返回元素的索引,初始為-1表示沒有。

expectedModCount用作和modCount進行對比,初始和modCount相等。

Iterator的關鍵方法

技術分享 技術分享
 1 public boolean hasNext() {
 2             return cursor != size;
 3         }
 4 
 5 public E next() {
 6             checkForComodification();
 7             int i = cursor;
 8             if (i >= size)
 9                 throw new NoSuchElementException();
10             Object[] elementData = ArrayList.this.elementData;
11             if (i >= elementData.length)
12                 throw new ConcurrentModificationException();
13             cursor = i + 1;
14             return (E) elementData[lastRet = i];
15         }
16 
17 public void remove() {
18             if (lastRet < 0)
19                 throw new IllegalStateException();
20             checkForComodification();
21 
22             try {
23                 ArrayList.this.remove(lastRet);
24                 cursor = lastRet;
25                 lastRet = -1;
26                 expectedModCount = modCount;
27             } catch (IndexOutOfBoundsException ex) {
28                 throw new ConcurrentModificationException();
29             }
30         }
技術分享

hasNext直接範圍其和size的比較值,若和size相等即表示到達最後,沒有下一個元素。

next先判斷是否有下個元素,如果沒有就拋出NoSuchElementException,

用i保存cursor,增加cursor然後直接返回ArrayList.this.elementData[i](當前對象的elementData),並將i的值賦給lastRet。

remove方法先判斷是否有上個返回的元素,沒有則拋出IllegalStateException,

調用當前對象的remove方法刪除上個元素,並把光標前移,將lastRet設為-1。

技術分享 技術分享
 1 public void forEachRemaining(Consumer<? super E> consumer) {
 2             Objects.requireNonNull(consumer);
 3             final int size = ArrayList.this.size;
 4             int i = cursor;
 5             if (i >= size) {
 6                 return;
 7             }
 8             final Object[] elementData = ArrayList.this.elementData;
 9             if (i >= elementData.length) {
10                 throw new ConcurrentModificationException();
11             }
12             while (i != size && modCount == expectedModCount) {
13                 consumer.accept((E) elementData[i++]);
14             }
15             // update once at end of iteration to reduce heap write traffic
16             cursor = i;
17             lastRet = i - 1;
18             checkForComodification();
19 }
技術分享

這是JAVA8添加了的一個缺省方法,只是遍歷所有元素然後用consumer.accept接受該元素。

另外還有相應的listiteror的實現,也僅是增加了hasPrevious和previous以及set和add方法,

前兩個和hasNext及next實現大同小異,後兩種也僅是調用了ArrayList對應的方法,在此就不作贅述。

ArrayList源碼分析