iterator
接口介紹iterator
接口,也是集合你們庭中的一員。和其餘的Map
和Collection
接口不一樣,iterator
主要是爲了方便遍歷集合中的全部元素,用於迭代訪問集合中的元素,至關於定義了遍歷元素的規範,而另外的Map
和Collection
接口主要是定義了存儲元素的規範。
還記得麼?以前說的iterable
接口,有一個方法就是叫iterator()
,也是返回iterator
對象。java
迭代:不斷訪問集合中元素的方式,取元素以前先判斷是否有元素,有則取出來,沒有則結束,不斷循環這個過程,直到遍歷完裏面全部的元素。 git
接口定義的方法以下: github
boolean hasNext(); // 是否有下一個元素 E next(); // 獲取下一個元素 // 移除元素 default void remove() { throw new UnsupportedOperationException("remove"); } // 對剩下的全部元素進行處理,action則爲處理的動做,意爲要怎麼處理 default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }
可是值得注意的是,集合類的總體不是繼承了iterator
接口,而是繼承了iterable
接口,經過iterable
接口的方法返回iterator
的對象。值得注意的是,iterator
的remove()
方法,是迭代過程當中惟一安全的修改集合的方法,爲什麼這樣說?
若是使用for循環索引的方式遍歷,刪除掉一個元素以後,集合的元素個數已經變化,很容易出錯。例如redis
for(int i=0;i<collection.size();i++){ if(i==2){ collection.remove(i); } }
而iterator
的remove()
方法則不會出錯,由於經過調用hasNext()
和next()
方法,對指針控制已經處理得比較完善。編程
首先,咱們知道iterator
接口是爲了定義遍歷集合的規範,也是一種抽象,把在不一樣集合的遍歷方式抽象出來,這樣遍歷的時候,就不須要知道不一樣集合的內部結構。 數組
爲何須要抽象?安全
假設沒有iterator
接口,咱們知道,遍歷的時候只能經過索引,好比數據結構
for(int i=0;i<array.size();i++){ T item = array[i]; }
這樣一來,耦合程度比較高,若是使用的數據結構變了,就要換一種寫法,不利於維護已有的代碼。若是沒有iterator
,那麼客戶端須要維護指針,至關於下放了權限,會形成必定程度的混亂。抽象則是把遍歷功能抽取出來,交給iterator
處理,客戶端處理集合的時候,交給更「專業」的它,it do it well.多線程
ListIterator
繼承於Iterator
接口,功能更強大,只能用於訪問各類List
類型,使用List
類型的對象list
,調用listIterator()
方法能夠獲取到一個指向list
開頭的ListIterator
分佈式
從上面圖片接口看,這個接口具備訪問下一個元素,判斷是否有下一個元素,是否有前面一個元素,判斷是否有前一個元素,獲取下一個元素的索引,獲取上一個元素的索引,移除元素,修改元素,增長元素等功能。和普通的Iterator
不同的是,ListIterator
的訪問指針能夠向前或者向後移動,也就是雙向移動。
boolean hasNext(); //是否還有元素 E next(); //獲取下一個元素 boolean hasPrevious(); //是否有上一個元素 E previous(); // 獲取上一個元素 int nextIndex(); //獲取下一個索引 int previousIndex(); //獲取上一個索引 void remove(); //移除 void set(E e); //更新 void add(E e); //添加元素
測試代碼以下:
List<String> list = new ArrayList<String>(Arrays.asList("Book","Pen","Desk")); // 把指針指向第一個元素 ListIterator<String> lit = list.listIterator(1); while(lit.hasNext()){ System.out.println(lit.next()); } System.out.println("==================================="); //指針指向最後一個元素列表中的最後一個元素修改ChangeDesk。 lit.set("ChangeDesk"); // 往前面遍歷 while(lit.hasPrevious()){ System.out.println(lit.previous()); }
輸出以下:
Pen Desk =================================== ChangeDesk Pen Book
若是點開ArrayList
的源碼,看到與ListIterator
相關的部分,咱們會發現其實ArrayList
在底層實現了一個內部類ListItr
,繼承了Itr
,實現了ListIterator
接口。這個Itr
其實就是實現了Iterator
,實現了基本的List迭代器功能,而這個ListItr
則是加強版的專門爲List
實現的迭代器。裏面使用cursor
做爲當前的指針(索引),全部函數功能都是操做這個指針實現。
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { super(); // 設置當前指針 cursor = index; } public boolean hasPrevious() { // 不是第一個元素就代表有前一個元素 return cursor != 0; } // 獲取下一個元素索引 public int nextIndex() { return cursor; } // 獲取前面一個元素索引 public int previousIndex() { return cursor - 1; } @SuppressWarnings("unchecked") 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]; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } 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(); } } }
咱們能夠看到,在上面方法中,有不少校驗,好比checkForComodification()
,意爲檢查是否被修改,list中的元素修改有可能致使數組越界。
準確地來講,SpitIterator
和Iterator
並無什麼關係,只是兩個功能上有相似。SpitIterator
主要是定義類將集合分割成多個集合,方便並行計算。
public interface Spliterator<T> { // 順序處理每個元素,參數是處理的動做,若是還有元素須要處理則返回true,不然返回false boolean tryAdvance(Consumer<? super T> action); // 依次處理剩下的元素 default void forEachRemaining(Consumer<? super T> action) { do { } while (tryAdvance(action)); } // 最重要的方法,用來分割集合 Spliterator<T> trySplit(); //估算還有多少元素須要遍歷處理 long estimateSize(); // 獲取準確的元素,若是不能獲取準確的,則會返回估算的 default long getExactSizeIfKnown() { return (characteristics() & SIZED) == 0 ? -1L : estimateSize(); } // 表示該Spliterator有哪些特性,這個像是個拓展功能,更好控制和優化Spliterator使用 int characteristics(); // 判斷是否有哪些特性 default boolean hasCharacteristics(int characteristics) { return (characteristics() & characteristics) == characteristics; } // 若是這個Spliterator的源具備已排序的特徵,那麼這個方法將返回相應的比較器。若是源按天然順序排序,則返回 // null。不然,若是源未排序,則拋出IllegalStateException。 default Comparator<? super T> getComparator() { throw new IllegalStateException(); } public static final int ORDERED = 0x00000010; public static final int DISTINCT = 0x00000001; public static final int SORTED = 0x00000004; public static final int SIZED = 0x00000040; public static final int NONNULL = 0x00000100; public static final int IMMUTABLE = 0x00000400; public static final int CONCURRENT = 0x00001000; public static final int SUBSIZED = 0x00004000; }
使用的方法例子以下:
public static void spliterator(){ List<String> list = Arrays.asList("1", "2", "3","4","5","6","7","8","9","10"); // 獲取可迭代器 Spliterator<String> spliterator = list.spliterator(); // 一個一個遍歷 System.out.println("tryAdvance: "); spliterator.tryAdvance(item->System.out.print(item+" ")); spliterator.tryAdvance(item->System.out.print(item+" ")); System.out.println("\n-------------------------------------------"); // 依次遍歷剩下的 System.out.println("forEachRemaining: "); spliterator.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); // spliterator1:0~10 Spliterator<String> spliterator1 = list.spliterator(); // spliterator1:6~10 spliterator2:0~5 Spliterator<String> spliterator2 = spliterator1.trySplit(); // spliterator1:8~10 spliterator3:6~7 Spliterator<String> spliterator3 = spliterator1.trySplit(); System.out.println("spliterator1: "); spliterator1.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator2: "); spliterator2.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator3: "); spliterator3.forEachRemaining(item->System.out.print(item+" ")); }
結果以下:
tryAdvance: 1 2 ------------------------------------------- forEachRemaining: 3 4 5 6 7 8 9 10 ------------------------------------------ spliterator1: 8 9 10 ------------------------------------------ spliterator2: 1 2 3 4 5 ------------------------------------------ spliterator3: 6 7
還有一些其餘的用法在這裏就不列舉了,主要是trySplit()以後,能夠用於多線程遍歷。理想的時候,能夠平均分紅兩半,有利於並行計算,可是不是必定平分的。
spliterator
能夠將其實現特徵表示爲同一接口中定義的一組常量。也就是咱們見到的ORDERED
,DISTINCT
,SORTED
,SIZED
之類的,這個意思是每個實現類,都有本身的實現方式,實現方式不一樣,實現特徵也不同,好比ArrayList
實現特徵是ORDERED
,SIZED
和SUBSIZED
,這個咱們能夠經過characteristics()
and hasCharacteristics()
來判斷。例如:
public static void main(String[] args) throws Exception{ List<String> list = new ArrayList<>(); Spliterator<String> s = list.spliterator(); System.out.println(s.characteristics()); if(s.hasCharacteristics(Spliterator.ORDERED)){ System.out.println("ORDERED"); } if(s.hasCharacteristics(Spliterator.DISTINCT)){ System.out.println("DISTINCT"); } if(s.hasCharacteristics(Spliterator.SORTED)){ System.out.println("SORTED"); } if(s.hasCharacteristics(Spliterator.SIZED)){ System.out.println("SIZED"); } if(s.hasCharacteristics(Spliterator.CONCURRENT)){ System.out.println("CONCURRENT"); } if(s.hasCharacteristics(Spliterator.IMMUTABLE)){ System.out.println("IMMUTABLE"); } if(s.hasCharacteristics(Spliterator.NONNULL)){ System.out.println("NONNULL"); } if(s.hasCharacteristics(Spliterator.SUBSIZED)){ System.out.println("SUBSIZED"); } }
輸出的結果是
16464 ORDERED SIZED SUBSIZED
輸出結果中的16464和其餘的怎麼掛鉤的呢?其實咱們發現上面的hasCharacteristics()
方法中,實現是return (characteristics() & characteristics) == characteristics;
,不難看出,這些狀態是根據與運算來計算出來的。上面的結果也代表ArrayList
有ORDERED
,SIZED
和SUBSIZED
這幾個特徵。
若是是HashSet
則特徵是DISTINCT
和SIZED
。
iterator
只是一個接口,至關於一個規範,全部的子類或者繼承類實現的時候理論上應該遵照,可是不同的繼承類/子類會有不同的實現。
iterator
只是一個接口,一個規範,雖然裏面有個別方法有默認實現,可是最重要也最豐富的的,是它在子類中的實現與拓展,如今來看在ArrayList
中的實現。ArrayList
並無直接去實現iterator
接口,而是經過內部類的方式來操做,內部類爲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; Itr() {} // 是否有下一個元素 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(); } } // 依次處理剩下的元素 @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } // 安全檢查,檢查是否被修改 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
從上面的源碼能夠看到,不少關於被修改的檢查,集合會追蹤修改(增刪改)的次數(modCount 又稱版本號),每個迭代器會單獨立維護一個計數器,在每次操做(增刪改),檢查版本號是否發生改變,若是改變,就會拋出ConcurrentModificationException() 異常,這是一種安全保護機制。
安全檢查,快速失敗機制實現主要和變量modCount
,expectedModCount
,以及一個checkForComodification()
方法有關,也就是expectedModCount
是內部類的修改次數,從字面意思看是指理論上期待的修改次數,modCount
是外部類的修改次數,建立的時候,會將modCount
賦值給expectedModCount
,二者保持一致,若是在迭代的過程當中,外部類的modCount
對不上expectedModCount
,n那麼就會拋出ConcurrentModificationException
異常。
首先,HashMap
裏面定義了一個HashIterator
,爲何這樣作呢?由於HashMap
存儲結構的特殊性,裏面有Entry<key,value>,因此遍歷就有三種狀況,一個是Key,一個是Value,另外一個就是Entry,這三個的迭代遍歷都有類似性,因此這裏根據抽象原則,定義了一個Hash迭代器。
abstract class HashIterator { // 下一個節點 Node<K,V> next; // 當前節點 Node<K,V> current; // current entry // 指望修改次數 int expectedModCount; // for fast-fail // 索引 int index; // current slot HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // 指向第一個不爲空的元素 do {} while (index < t.length && (next = t[index++]) == null); } } // 是否有下一個節點 public final boolean hasNext() { return next != null; } // 獲取下一個節點 final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; } // 移除 public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } }
以後分別定義KeyIterator
,ValueIterator
,EntryIterator
,繼承於HashIterator
,
// 遍歷key final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } // 遍歷value final class ValueIterator extends HashIterator implements Iterator<V> { public final V next() { return nextNode().value; } } //遍歷entry final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } }
以上的種種,關於Iterator
,其實就是一個迭代器,可簡單地理解爲遍歷使用,主要功能是指向一個節點,向前或者向後移動,若是數據結構複雜就須要多個迭代器,好比HashMap
,能夠避免多個迭代器之間相互影響。每個迭代器都會有
expectedModCount 和modCount,就是校驗這個迭代過程當中是否被修改,若是修改了,則會拋出異常。
【做者簡介】:
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。我的寫做方向:Java源碼解析,JDBC,Mybatis,Spring,redis,分佈式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裏胡哨,大多寫系列文章,不能保證我寫的都徹底正確,可是我保證所寫的均通過實踐或者查找資料。遺漏或者錯誤之處,還望指正。
平日時間寶貴,只能使用晚上以及週末時間學習寫做,關注我,咱們一塊兒成長吧~