後端---Java中Iterator(迭代器)原理分析
Java中Iterator(迭代器)的用法及其背後機制的探究
一.背景延伸
在Java中遍歷List時會用到Java提供的Iterator,Iterator十分好用,原因是:
迭代器是一種設計模式,它是一個物件,它可以遍歷並選擇序列中的物件,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”物件,因為建立它的代價小。
Java中的Iterator功能比較簡單,並且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次呼叫Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable介面,被Collection繼承。
(2) 使用next()獲得序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將迭代器新返回的元素刪除。
二.為什麼使用迭代器?
因為最初的時候你用for迴圈遍歷List,之後人家又要你遍歷Set,但是for迴圈無法遍歷Set,因為Set是無序的(無法get值),所以後面就統一用迭代器遍歷集合了。
三.迭代器迭代順序
HashMap按鍵的順序,HashSet按hashCode的順序。
四.Iterator的使用
(一)Iterator在Collection介面中的使用。
雖然Collection介面的相關類實現了get()方法,但將Iterator用在它們身上仍然是合適的,下面以ArrayList為例,討論Iterator在Collection中的兩中使用方法:
1.配合while()迴圈實現遍歷輸出:
1 ArrayList list = new ArrayList();
2 //此處省略list的具體賦值過程
3 Iterator it = list.iterator();
4 while(it.hasNext()){
5 System.out.println(it.next());
6 }
while()中的判斷條件it.hasNext()用於判斷it中是否還有下一元素,有的話就繼續迴圈,輸出語句中的it.next()既可以使“指標”往後走一位,又能將當前的元素返回,用於輸出。
不過上面的是在一般while()迴圈中的使用,我們還可以用for each 迴圈來代替Iterator,因為for each 本身就相當於一個迭代器了:
1 ArrayList list = new ArrayList();
2 //此處同樣省略list的賦值過程
3 for(Object array:list){
4 System.out.println(array);
5 }
需要注意的是for each 不適合用來增刪元素,如果單純用來遍歷元素,這種寫法也許會更簡潔一點。
(二)Iterator在Map介面中的使用:
下文用HashMap為例,討論迭代器的兩種主要使用方法。
1.與while()的結合
HashMap<K,V> myMap = new HashMap<K,V>();
//省略myMap的的賦值過程
Iterator<Map.Entry<K,V> it=myMap.entrySet().iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//如果想讓輸出更加格式化,可以自己重寫toString()方法,由於toString方法的重寫不是本文討論的重點,所以暫且不討論。
Map介面下的iterator應用方法與Collection中的略微有些不同,用到了entrySet()方法,entrySet()用來返回整個鍵—值對。
同樣這裡再貼出for each代替Iterator的用法:
1 HashMap<K,V> myMap=new HashMap<K,V>();
2 //省略myMap賦值過程
3 for(Object oj:myMap.entrySet()){
4 System.out.println(oj);
5 }
五.Iterator的原理
所有Iterator都最終實現介面Iterator,Iterator介面中包含三個基本方法,next(), hasNext(), remove(),其中對於List的遍歷刪除只能用Iterator的remove方法;JDK1.8中Iterator介面的原始碼如下:
public interface Iterator<E> {
boolean hasNext();
// JDK1.8的新特性,可以通過default在介面中寫個方法的實現
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
下面將基於ArrayList的Iterator的實現分析Iterator的原理(基於JDK1.8):
在ArrayList類中有個方法iterator(),此方法將返回一個iterator的實現,這裡可以看出實現類叫Itr,通過其它原始碼可知,此類是AarryList的內部類,即ArryList的Iterator實現在ArrayList內部;
這時ArrayList返回一個Iterator迭代器的方法:
public Iterator<E> iterator() {
return new Itr();
}
下面重點看下ArrayList中實現類Itr類的原始碼:
private class Itr implements Iterator<E> {
/**
* 下一個返回的位置
*/
int cursor = 0;
/**
* 當前操作的位置
*/
int lastRet = -1;
/**
* 類似版本號,檢查List是否有更新
*/
int expectedModCount = modCount;
public boolean hasNext() { // 判斷是否有下一個元素
return cursor != size();
}
public E next() { // 返回下一個元素
checkForComodification();
try {
int i = cursor; // cursor記錄的是下一個元素,所以呼叫next時將返回的是cursor對應的元素
E next = get(i); // 記錄需要返回的元素
lastRet = i; // 記錄當前元素
cursor = i + 1; // 記錄下一個元素
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() { // 移除元素
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); // 檢查是否有更改,remove或者add
try {
AbstractList.this.remove(lastRet); // 刪除當前元素
if (lastRet < cursor) // 刪除了之後指標減1
cursor--;
lastRet = -1;
expectedModCount = modCount; // 保持版本號一致
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() { // 如果有更改則丟擲ConcurrentModificationException異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
從上面程式碼中可以看出,對於Iterator的實現中主要有幾個變數cursor,lastRest, expectedModCount三個變數,其中cursor將記錄下一個位置,lastRet記錄當前位置,expectedModCount記錄沒有修改的List的版本號。
問題:還記得說List中在iterator遍歷的時候,不能隨便新增和刪除元素嗎,這是為什麼呢?
在iterator遍歷的時候丟擲異常都是checkForComodification作的,根本原因是modCout和expectedModCount不相等,導致丟擲異常
那為啥會不相等呢?
可以看看ArrayList的add和remove方法,
add方法:
從上面的程式碼中可以看出只要對ArrayList作了新增或刪除操作都會增加modCount版本號,這樣的意思是在迭代期間,會不斷檢查modCount和迭代器持有的expectedModCount兩者是不是相等,如果不想的就丟擲異常了。
這樣在迭代器迭代期間不能對ArrayList作任何增刪操作,但是可以通過iterator的remove作刪除操作,從之前的程式碼可以看出,在iterator的remove()中有一行程式碼,expectedModCount = modCount; 這個賦值操作保證了iterator的remove是可用性的。
當然,iterator期間不能增刪的根本原因是ArrayList遍歷會不準,就像遍歷陣列的時候改變了陣列的長度一樣
參考博文:
https://blog.csdn.net/qq_36101933/article/details/82632137
https://www.cnblogs.com/TangMoon/p/7589082.html
https://blog.csdn.net/qq_36470686/article/details/85065545