通過ArrayList原始碼深入理解java中Iterator迭代器的實現原理
注意:本文將著重從原始碼的角度對Iterator的實現進行講解,不討論List與Iterator介面的具體使用方法。不過看懂原始碼後,使用也就不是什麼問題了。
java中各種實現Iterator的類所具體使用的實現方法各不相同,但是都大同小異。因此本文將只通過ArrayList類原始碼進行分析。所以最好對ArrayList的原始碼有一定了解,或者至少具備相關的演算法知識。
首先貼出ArrayList類中與Iterator有關的程式碼。(不同版本jdk可能有微小差別)
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
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
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
很明顯,它是返回了一個內部類,通過這個內部類來實現。
由於需要考慮到各種各樣的情況,java中的原始碼總是會多出很多出於完善考慮的程式碼,非常影響理解功能實現的本質。這個時候如果想深入理解,最好的方法就是自己實現一個簡化版本,以此為基礎進行分析。
這是我寫的一個只實現了Iterable的簡化版ArrayList。
import java.util.Iterator;
public class Nayi224ArrayList implements java.lang.Iterable{
//ArrayList實際上是一個維護Object[]的類,elementData才是本體。這裡先把資料寫死。
public Object[] elementData = {1, 2, 3, 4};
//平常所用的 .size() 其實就是return size,也先提前寫死。
public int size = 4;
public Iterator iterator(){
return new IteratorImpl();
};
private class IteratorImpl implements java.util.Iterator{
private int cursor; //遊標。int型成員變數預設初始值為0。
public boolean hasNext() {
return cursor != size;
}
public Object next() {
return elementData[cursor++];
}
public void remove() {
/*
* ArrayList中remove操作的核心程式碼的改寫。
* 完整版還包括下標越界校驗,modCount自增(fail-fast),末位置空(for gc)。均已省略。
*
*
* System.arraycopy的用法可以直接檢視註釋獲得。
*
* */
System.arraycopy(elementData, cursor, elementData, cursor - 1, size - (cursor - 1) - 1);
}
}
}
所有程式碼都簡化成了一行,即使是新手也能看懂,這裡就不做多餘講解了。
在使用的時候,與原版完全一樣。(remove方法只實現了一半,與原版有較大差異)
public class BlogTest {
public static void main(String[] args) {
Nayi224ArrayList list = new Nayi224ArrayList();
java.util.Iterator it = list.iterator();
while (it.hasNext())
System.out.println(it.next());
}
}
這就是實現迭代功能的全部核心程式碼。
現在開始逐步理解原始碼中的其他部分。
首先是有名的fase-fail機制。它的作用是在迭代的時候,如果list發生了結構上的變化,將會丟擲異常。在ArrayList.Itr中主要通過這幾句話來實現。
int expectedModCount = modCount;
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount是ArrayList所繼承的屬性,在對ArrayList進行結構上的修改,比如add,remove等方法時,會modCount++。
expectedModCount 是ArrayList的內部類Itr的屬性,會在初始化的時候賦值為外部類的modCount。
注意:雖然是int型別,但是這並不是值傳遞,而是一種特殊的引用傳遞。這與成員內部類的特性有關。通過外部類建立的內部類會保留外部類的引用。
int expectedModCount = modCount;
的另一種比較正規的寫法是
int expectedModCount = ArrayList.this.modCount;
如果用反編譯工具看的話可能會是這種東西this.this$0.modCount
。它是一種地址引用,這一點是理解快速失敗機制的關鍵。
checkForComodification()出現於Itr類中next和remove的開始部分。如果在操作一個Iterator的過程中對建立它的外部類進行了結構上的修改,將會丟擲ConcurrentModificationException異常。
我經常看到有人從併發的角度來講解Iterator的快速失敗機制。這很貼近現實,但是卻遠離了本質。這跟多執行緒有什麼關係呢,不過是迭代的時候ArrayList被修改了而已。(需要注意的是Itr的remove方法呼叫了外部類的remove方法,同樣會導致modCount++)。
如果你想搞炸一個程式,只需要這4行程式碼
List arr = new ArrayList(Arrays.asList(1, 2, 3, 4));
Iterator it =arr.iterator();
arr.add(1);
it.next();
初始化–add–迭代–boom!
相信很多人都在無數的資料中看到過類似的一句話。
迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗迭代器會盡最大努力丟擲ConcurrentModificationException。
但是至少我還沒看到過有誰對這段話做過解釋。到底是在什麼情況下,它才會即使“盡最大努力”也無法丟擲異常。不過在看完原始碼後發現,這個問題好像確實簡單到不需要做特別說明的程度。
在remove方法中
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
//執行緒B進行add操作。
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
如果執行緒A在執行完checkForComodification();
後,執行緒B立刻執行完了一次add,由於執行緒A重新對expectedModCount進行了賦值,這將導致無法對這次不同步的修改丟擲異常。這可能會導致意想不到的bug發生。
同樣的,在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];
}
如果A執行緒在執行完第一行後發生了阻塞,而B執行緒在這時正好完成了一次add和一次remove。list的結構顯然改變了。A執行緒可能返回了B執行緒新新增的物件,卻無法丟擲異常。如果A執行緒繼續呼叫next方法,它將在下一次呼叫時丟擲異常,這與事實不符。
在Itr類中有這麼一個屬性
int lastRet = -1; // index of last element returned; -1 if no such
它主要出現在remove方法中。
//初始值為-1
int lastRet = -1; // index of last element returned; -1 if no such
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();
}
}
從程式碼上看,它的作用就兩個。一個是使剛初始化的迭代器直接呼叫remove時報錯,另一個是在正常呼叫remove後再掉一次remove引發報錯。個人覺得這應該是為了符合某種規範吧。
第一種報錯。
List arr = new ArrayList(Arrays.asList(1, 2, 3, 4));
Iterator it =arr.iterator();
it.remove();
第二種報錯。
List arr = new ArrayList(Arrays.asList(1, 2, 3, 4));
Iterator it =arr.iterator();
it.next();
it.remove();
it.remove();
重要的大概就這麼多了,剩下的基本都是些校驗下標越界或是一些更具體的實現。隨便看看也就懂了。