以腦圖的形式來展示Java集合知識,讓零碎知識點形成體系 Iterator 對比 Iterator(迭代器)是一種設計模式,是一個對象,用於遍歷集合中的所有元素。 Iterator 包含四個方法,分別是:next()、hasNext()、remove()、forEachRemaining(Consu ...
以腦圖的形式來展示Java集合知識,讓零碎知識點形成體系
Iterator 對比
Iterator(迭代器)是一種設計模式,是一個對象,用於遍歷集合中的所有元素。
Iterator 包含四個方法,分別是:next()、hasNext()、remove()、forEachRemaining(Consumer<? super E> action)
Collection 介面繼承 java.lang.Iterable,因此所有 Collection 實現類都擁有 Iterator 迭代能力。
逆向思考,Iterable 面向眾多的 Collection 類型實現類,定義的方法就不可能太定製化,因此 Iterator 定義的功能比較簡單。
僅有如上所列出來的四種方法,並且該迭代器只能夠單向移動。
由於 List 類型的 Collection 是一個有序集合,對於擁有雙向迭代是很有意義的。
ListIterator 介面則在繼承 Iterator 介面的基礎上定義了:add(E newElement)、set(E newElement)、hasPrevious()、previous()、nextIndex()、previousIndex() 等方法,使得 ListIterator 迭代能力增強,能夠進行雙向迭代、迭代過程中可以進行增刪改操作。
現象與問題
- add() 方法在迭代器位置前面添加一個新元素
- next() 與 previous() 返回越過的對象
- set() 方法替換的是 next() 和 previous() 方法返回的上一個元素
- next() 後,再 remove() 則刪除前面的元素;previous() 則會刪除後面的元素
1 List<String> list = new LinkedList<>(); 2 list.add("aaa"); 3 list.add("bbb"); 4 list.add("ccc"); 5 6 ListIterator<String> listIterator = list.listIterator(); 7 8 //迭代器位置: add-1 | aaa bbb ccc 9 listIterator.add("add-1"); 10 // add-1 add-1 | aaa bbb ccc 11 listIterator.add("add-2"); 12 13 // 返回: aaa 14 // add-1 add-1 aaa | bbb ccc 15 listIterator.next(); 16 // add-1 add-1 aaa-set | bbb ccc 17 listIterator.set("aaa-set"); 18 // bbb 19 // add-1 add-1 aaa-set bbb | ccc 20 listIterator.next(); 21 22 // 返回: bbb 23 // add-1 add-1 aaa-set | bbb ccc 24 listIterator.previous(); 25 // add-1 add-1 aaa-set | bbb-set ccc 26 listIterator.set("bbb-set"); 27 28 // 刪除 bbb-set 29 listIterator.remove(); 30 listIterator.remove(); 31 32 System.out.println(list);
很多書本都有給出這樣子的結論:
-
鏈表有 n 個元素,則有 n+1 個位置可以添加新元素;
-
add() 方法只依賴迭代器的+位置;remove() 和 set() 方法依賴於迭代器的狀態(此時迭代的方向);
-
連續兩個 remove() 會出錯,remove() 前應先執行 next() 或 previous()。
迭代同時修改問題:
一個迭代器指向另一個迭代器剛剛刪除的元素,則現在這個迭代器就變成無效的了(節點刪除被回收;即使沒被回收,該節點的前後引用也被重置為null)。
鏈表迭代器有能夠檢測到這種修改的功能,當發現集合被修改了,將會拋出一個 ConcurrentModificationException 異常
為什麼出現上面的這些現象與問題呢,我們還是從源碼中尋找答案吧
源碼分析
有多個集合類根據自己的特點實現了 ListIterator 介面,其實現都大同小異,這裡我們主要分析 LinkedList 中所實現的 ListIterator。
首先我們來分析 LinkedList 的 listIterator() 和 listIterator(int index) 方法獲取 ListIterator 迭代器過程。
1 // AbstractList.java 2 // listIterator() 方法 LinkedList 類本身並沒有重寫,需要追溯到 AbstractList 抽象類 3 4 // 獲取 ListIterator 迭代器 5 public ListIterator<E> listIterator() { 6 return listIterator(0); 7 } 8 9 public ListIterator<E> listIterator(final int index) { 10 rangeCheckForAdd(index); // 檢查 index 範圍是否超出 11 12 return new ListItr(index); // 該抽象類也有實現 ListItr 類 13 } 14 15 private void rangeCheckForAdd(int index) { 16 if (index < 0 || index > size()) 17 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 18 }
1 // LinkedList.java 2 // LinkedList 類重寫了 listIterator(int index) 方法 3 4 public ListIterator<E> listIterator(int index) { 5 checkPositionIndex(index); // 同理 檢查 index 範圍;相關代碼就不貼了 6 return new ListItr(index); 7 } 8 9 10 private class ListItr implements ListIterator<E> { 11 private Node<E> lastReturned; // 上一次處理的節點 12 private Node<E> next; // 即將要處理的節點 13 private int nextIndex; // 即將要處理的節點的 index 14 // modCount 表示集合和迭代器修改的次數;expectedModCount 表示當前迭代器對集合修改的次數 15 private int expectedModCount = modCount; 16 17 ListItr(int index) { 18 // assert isPositionIndex(index); 19 next = (index == size) ? null : node(index); 20 nextIndex = index; 21 } 22 23 public boolean hasNext() { 24 return nextIndex < size; 25 } 26 27 /** 28 * 處理對象:迭代器當前的 next 節點 29 * 將處理目標儲到 lastReturned 變數中 30 * 然後將當前的 next.next 節點保存起來,用於下一次迭代處理 31 * nextIndex 同時 +1 32 * 返回 lastReturned.item 元素 33 * 執行後:lastReturned 指向該次處理的節點;next、nextIndex 指向該次處理節點的後一個節點 34 */ 35 public E next() { 36 // 檢查 modCount 與 expectedModCount 是否相等 37 // 實際檢查該鏈表是否被其他迭代器或者集合本身修改 38 checkForComodification(); 39 // 判斷是否存在 next 節點 40 if (!hasNext()) 41 throw new NoSuchElementException(); 42 43 lastReturned = next; // 將這次返回的 node 節點更新到迭代器中的 lastReturned 變數 44 next = next.next; // 將下一次需要處理 node 節點更新會 next 變數 45 nextIndex++; // 變數 nextIndex +1 46 return lastReturned.item; // 返回元素 47 } 48 49 public boolean hasPrevious() { 50 return nextIndex > 0; 51 } 52 53 /** 54 * 處理對象:迭代器當前的 next.prev 節點 55 * 將處理目標儲到 lastReturned 變數中 56 * 然後將當前的 next.prev 節點保存起來,用於下一次迭代處理 57 * nextIndex 同時 -1 58 * 返回當前的 next.item 元素 59 * 執行後:next、lastReturned、nextIndex 指向該次處理節點的前一個節點 60 */ 61 public E previous() { 62 checkForComodification(); 63 // 判斷是否存在 prev 節點 64 if (!hasPrevious()) 65 throw new NoSuchElementException(); 66 67 // 處理當前 next 的 prev 節點 68 // 特殊情況:next = null 時,則它的 prev 節點為 last 節點 69 lastReturned = next = (next == null) ? last : next.prev; 70 nextIndex--; // nextIndex -1 71 return lastReturned.item; 72 } 73 74 public int nextIndex() { 75 return nextIndex; 76 } 77 78 public int previousIndex() { 79 return nextIndex - 1; 80 } 81 82 /** 83 * 處理對象:lastReturned 84 * 刪除 lastReturned 指向的節點,並置為 null 85 * 同時保證 next 和 nextIndex 指向同一個節點 86 */ 87 public void remove() { 88 checkForComodification(); // 同理, 檢查 modCount 與 expectedModCount 是否相等 89 if (lastReturned == null) 90 throw new IllegalStateException(); 91 92 Node<E> lastNext = lastReturned.next; // 暫存 lastReturned 的 next 節點,用於恢復迭代狀態 93 unlink(lastReturned); // 刪除最後返回的節點 modCount++; 94 95 // 分迭代方向處理(因為刪除一個節點後,需要恢復迭代狀態:next 和 nextIndex 指向同一個節點) 96 if (next == lastReturned) // next 與 lastReturned 節點相同則表明最近一次迭代操作是 previous() 97 next = lastNext; // 刪除了原有 next 指向的節點,因此 nextIndex 相對指向的節點變為 next.next,需要更新 next 變數的指向 98 else 99 nextIndex--; // next() 迭代方向;刪除了next前面的節點,因此next的相對位置發生變化,需要 nextIndex -1 100 lastReturned = null; 101 expectedModCount++; // 同時 expectedModCount++ 102 } 103 104 /** 105 * 處理對象:lastReturned 106 */ 107 public void set(E e) { 108 if (lastReturned == null) 109 throw new IllegalStateException(); 110 checkForComodification(); 111 lastReturned.item = e; 112 } 113 114 /** 115 * 分位置進行添加 116 */ 117 public void add(E e) { 118 checkForComodification(); 119 lastReturned = null; 120 if (next == null) 121 linkLast(e); 122 else 123 linkBefore(e, next); 124 nextIndex++; 125 expectedModCount++; 126 } 127 128 public void forEachRemaining(Consumer<? super E> action) { 129 Objects.requireNonNull(action); 130 while (modCount == expectedModCount && nextIndex < size) { 131 action.accept(next.item); 132 lastReturned = next; 133 next = next.next; 134 nextIndex++; 135 } 136 checkForComodification(); 137 } 138 139 /** 140 * 檢查 modCount 與 expectedModCount 是否相等,否則拋出錯誤 141 * ListIterator 迭代器進行增刪操作時,都會同時對這兩個變數 +1 142 * 目的: 143 * 使用 ListIterator 迭代器期間,LinkedList 對象有且只能當前這一個迭代器可以進行修改 144 * 避免 LinkedList 對象本身以及其他迭代器進行修改導致鏈表混亂 145 */ 146 final void checkForComodification() { 147 if (modCount != expectedModCount) 148 throw new ConcurrentModificationException(); 149 } 150 }
小結
總的來說 ListIterator 是記錄 List 位置的一個對象,它主要的成員變數是 lastReturned、next、nextIndex 以及 expectedModCount。
next() 處理的是 next 節點,返回 next.item
previous() 處理的是 next.prev 節點 返回 next.prev.item
remove() 處理的是 lastReturned 節點,並置為null,但要註意的是,刪除節點後的 next 與 nextIndex 需分情況處理。
set() 處理的是 lastReturned 節點,lastReturned.item = e
add() 添加,並將 lastReturned 置為null
這就很好地解釋上面所提到的一些現象與問題了。
典型的就是連續兩個 remove() 會報錯,那是因為第一個 reomve() 之後 lastReturned 被置為null;第二個 remove() 處理的對象是null,因此炮錘 IllegalStateException
知識腦圖
在 github 上建了一個 repository ,Java Core Knowledge Tree,各位看官若是喜歡請給個star,以示鼓勵,謝謝。
https://github.com/suifeng412/JCKTree
(以上是自己的一些見解,若有不足或者錯誤的地方請各位指出)
作者:那一葉隨風 http://www.cnblogs.com/phpstudy2015-6/
原文地址: https://www.cnblogs.com/phpstudy2015-6/p/10660457.html
聲明:本博客文章為原創,只代表本人在工作學習中某一時間內總結的觀點或結論。轉載時請在文章頁面明顯位置給出原文鏈接