ArrayList原始碼分析(二)
今天來進行ArrayList中關於迭代器方面的相關分析。
Iterator
初始化以及內部屬性詳情
public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return intlastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; // prevent creating a synthetic constructor Itr() {} //其它程式碼 }
關於迭代器,可以從以上程式碼看出其內部並沒有定義實際的資料之類的,只定義了幾種方法,如cursor,lastRet,expectedModCount,用來提供迭代ArrayList物件的機制。
next方法
public E next() {
//檢查是否被併發修改 checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length)throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); //當被併發修改時丟擲異常 }
next 方法是實際用來迭代的方法,返回迭代的元素,並且讓指標指向下一個元素。在進行一開始的檢測後(如上註釋所示),“if (i >= size)” 用來判斷是否迭代完成,之後的“if (i >= elementData.length)”,因為到這裡的前提是i < size,若滿足i >= elementData.length這個條件說明size > elementData.length,丟擲併發修改異常。之後cursor+1,將之前的cursor值賦給lastRet。
remove方法
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
首先檢查lastRet是否小於0,lastRet小於0,要麼是還沒開始迭代時,為-1,要麼是已經呼叫過一次remove,會被再次設定為-1(後面程式碼就是)。這也可以發現,沒辦法連續兩次呼叫remove。接著檢查併發修改。之後呼叫ArrayList物件自己的remove方法來進行刪除,然後更新cursor和lastRet的值:cursor設定為lastRet,也就是自減了1,由於ArrayList的remove會把刪除元素後面的元素都往前移一位,所以cursor對應的元素仍然沒變。最後會設定迭代器的expectedModCount,因為ArrayList的remove會修改modCount值。所以我們發現,如果在用迭代器迭代元素時,想刪除元素,可以呼叫迭代器的remove方法,這不會導致後面的迭代丟擲異常;如果呼叫ArrayList的remove,會導致expectedModCount和modCount值不一致,迭代器就無法再使用了。
forEachRemaining方法
public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); final int size = ArrayList.this.size; int i = cursor; if (i < size) { final Object[] es = elementData; if (i >= es.length) throw new ConcurrentModificationException(); for (; i < size && modCount == expectedModCount; i++) action.accept(elementAt(es, i)); // update once at end to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } }
關於此部分程式碼,關鍵還是在for迴圈這塊,一開始將cursor賦給i,故此迴圈的範圍為cursor到size-1,迴圈中進行的操作為action.accept(elementAt(es, i)),我們可以點accept進去看下,其為Consumer函式式介面中的一個抽象方法。
我們可以這樣呼叫 forEachRemaining :iter.forEachRemaining(System.out::println);,這樣就可以快速的列印iter裡剩下的元素,其實是用方法引用來代替實現了Consumer介面的類的物件,也可以自行編寫lambda表示式,當然,也可以定義一個Consumer介面的實現類。
ListIterator
初始化以及內部屬性詳情
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { super(); cursor = index; } public ListIterator<E> listIterator(int index) { rangeCheckForAdd(index); return new ListItr(index); } public ListIterator<E> listIterator() { return new ListItr(0); }
如上所示,ListIterator根據指定的index進行迭代。
previous方法
public E previous() { checkForComodification(); int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i; return (E) elementData[lastRet = i]; }
跟next方法大部分程式碼相同,本方法返回的是cursor指向元素的前一個元素,正如其名。
set方法
public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
set方法是用來進行替換操作的方法,要求lastRet >= 0,當然也有併發修改的相關的檢測。
add方法
public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
呼叫ArrayList自己的add之後,會讓cursor自增一個,由於add 會把index之後的元素都向後挪一位,所以cursor還是指向它之前指向的元素,最後設定expectedModCount。
SubList
SubList的執行結果是獲取ArrayList的一部分,返回的是ArrayList的部分檢視。
初始化以及內部屬性詳情
public List<E> subList(int fromIndex, int toIndex) {
//fromIndex---擷取元素的起始位置,包含該索引位置元素
//toIndex-----擷取元素的結束位置,不包含該索引位置元素 subListRangeCheck(fromIndex, toIndex, size); return new SubList<>(this, fromIndex, toIndex); } private static class SubList<E> extends AbstractList<E> implements RandomAccess { private final ArrayList<E> root; private final SubList<E> parent; private final int offset; private int size; /** * 從ArrayList建立SubList. */ public SubList(ArrayList<E> root, int fromIndex, int toIndex) { this.root = root; this.parent = null; this.offset = fromIndex; this.size = toIndex - fromIndex; this.modCount = root.modCount; } /** * 從SubList再建立SubList. */ private SubList(SubList<E> parent, int fromIndex, int toIndex) { this.root = parent.root; this.parent = parent; this.offset = parent.offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = parent.modCount; }
由於SubList繼承了AbstractList類,所以它自己也有一個modCount屬性;可以選擇由ArrayList或者subList來建立一個新的subList。首先看由ArrayList初始化subList,畢竟你需要有第一個subList,才能用subList初始化出其它的subList。可以看到,這裡把root 設定為這個ArrayList物件本身,parent設定為null,然後分別設定offset,size和modCount。
再看由SubList初始化SubList,可以發現,root還是那個root,不管你subList嵌套了多少層,parent就是此subList上面一層,offset就是此subList相對於原始ArrayList的偏移量,層層疊加,size就是subList的長度,modCount和parent的保持一致。
get方法
public E get(int index) { Objects.checkIndex(index, size); checkForComodification(); return root.elementData(offset + index); }
get方法較為簡單,首先還是老樣子,進行相關檢測,如同步檢測,越界檢測,然後以offset + index作為索引的下標返回,offset相當於偏移量。
add方法
public void add(int index, E element) { rangeCheckForAdd(index); checkForComodification(); root.add(offset + index, element); updateSizeAndModCount(1); }
private void updateSizeAndModCount(int sizeChange) {
SubList<E> slist = this;
//改變此層及以上的size和modCount
do {
slist.size += sizeChange;
slist.modCount = root.modCount;
slist = slist.parent;
} while (slist != null);
}
該方法主要還是靠呼叫root的add即ArrayList本身的add的方法,主要還是看最後的updateSizeAndModCount(1),遞迴的改變這層和以上層的size和modCount,從這也可以發現,此層以下的subList就不管了,所以如果subList嵌套了許多層,需要用subList進行結構性修改的話,最好用最下面那層來改,不然,下面的subList就都廢掉了。
還有其他方法,但大都大同小異,就不具體分析了。最後關於迭代器中的for-each迴圈, 其實就是新建了一個迭代器,不斷進行hasNext()
和next()
的呼叫。
就這樣吧,這些原始碼分析主要還是自用