五合一平臺源碼搭建下載
叠代器模式
叠代器模式(Iterator Pattern) 又稱遊標(Cursor) 模式,是行為型設計模式之一。叠代器模式源於對容器的訪問,比如 Java 中的 List、Map、數組等,我們知道對容器對象的訪問必然會涉及到遍歷算法,我們可以將遍歷的方法封裝在容器中,或者不提供便利方法。如果我們將便利方法封裝在容器中,那麽對於容器來說承擔了過多的功能,容器類不僅要維護自身內部的數據元素而且還要對外提供遍歷的接口方法,因為遍歷狀態的存儲問題還不能對同一個容器同時進行多個便利操作,如果我們不提供便利方法而讓使用者自己去實現,又會讓容器內部細節暴露,因此,叠代器模式產生,在客戶訪問類與容器之間插入了一個第三者 - 叠代器,很好的解決了上述的弊端。
二、Java 中的叠代器類
簡單了解了叠代器模式之後,我們來分析 Java 中是如何具體通過叠代器模式實現了對集合的遍歷,以及由 Java 中的實現方式更深入的理解叠代器模式的應用。
- Iterable
Iterable 是一個接口,實現了該接口的類是可以通過叠代器進行遍歷的,Java 中的 Collection 繼承了 Iterable,List 和 Set 有繼承了 Collection,所以 List 和 Set 中元素的遍歷都是可以通過叠代器模式來實現。
Iterable 的 iterator() 方法的作用就是返回一個叠代器,最終使用叠代器完成遍歷工作。
public interface Iterable<T> {
- 返回一個叠代器 Iterator 用於元素集合的遍歷
*/
Iterator<T> iterator();
}
1
2
3
4
5
6- Iterator
Iterator 類是 Java 中叠代器的接口,Iterator 類中聲明了 hashNext() 和 next() 兩個重要方法,來判斷是否遍歷結束以及遍歷到下一個位置。
public interface Iterator<E> {
boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java 中的集合實現主要 List 和 Set,下面會先分析 List 中叠代器的使用,然後再由 Set 的叠代器分析延伸到 Map 的叠代方式。
三、List 中的叠代器 Iterator
前面提到了 List 繼承了 Iterable,所以也要實現 iterator() 方法用來返回一個可用的叠代器,方法如下,直接返回一個 Itr 對象,重點來分析 Itr 類的實現。
public Iterator<E> iterator() {
return new Itr();
}
1
2
3
private class Itr implements Iterator<E> {
/**
-
下一次遍歷返回的元素的索引
*/
int cursor = 0;/**
-
返回上一次 next 返回的元素的索引,如果該元素被刪除則返回 -1
*/
int lastRet = -1;/**
-
叠代器中記錄集合的命中數量,在叠代器操作時如果集合的命中數量與記錄的不同,此時叠代器會檢測到並發修改
*/
int expectedModCount = modCount;/**
-
返回是否還有元素沒有被遍歷
*/
public boolean hasNext() {
return cursor != size();
}/**
-
返回是下一個需要被遍歷的元素
*/
public E next() {
// 檢測
checkForComodification();
try {
int i = cursor;
// 獲取當前需要被遍歷的元素
E next = get(i);
// 記錄當前被遍歷的元素索引
lastRet = i;
// 計算下一個需要被遍歷的元素索引
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}/**
- 叠代過程中,從集合中移除當前遍歷到的元素的方法
-
在遍歷時,通過叠代器的 remove() 方法可以安全的移除元素,如果不使用叠代器的 remove() 方法,會造成並發操作,拋出異常
*/
public void remove() {
if (lastRet < 0) // 還沒有開始遍歷,做移除操作會拋出異常
throw new IllegalStateException();
checkForComodification();try { // 調用集合本身的移除方法移除元素 AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; // 更新期望的命中數量 expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); }
}
/**
- 檢測集合的命中數量是否於記錄的相同,如果不同則說明有並發修改,拋出異常
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
List 的叠代器的實現中,hasNext() 方法是通過當前遍歷到的位置是否超過集合的數量來判斷的,next() 方法則是返回當前遍歷到的位置的元素,在操作過程中都需要進行是否存在並發修改的判斷,這個判斷通過對比叠代器中記錄的集合命中數量與真是的命中數量比較來確定,在 List 的叠代器的 remove() 方法中,出了進行移除操作,還會更新叠代器中保存的命中數量。
在開發過程中,對集合的遍歷我們一般都是通過 for 循環的形式,但是 for 循環的方式在遍歷過程中如果我們需要移除元素,則需要添加其他的處理(對 for 循環條件的動態調整,因為移除元素後集合數量已經發生了變化,如果不調整會在遍厲過程中跑出 IndexOutOfBoundsException 異常),而使用叠代器的方式則不需要做特殊處理。
四、Set 中的叠代器
List 的叠代器的代碼比較簡單,因為 Set 這種數據結構是通過 Map 實現的,所以 Set 的遍歷則相對復雜一點,這部分不熟悉的同學,可以出門左轉,看看我之前寫過的一篇介紹 Java 中提供的集合和 Map 實現方式的文章,地址在這裏:Java 集合類實現原理
接下來我們以 HashSet 為例看一下 Set 中叠代器的工作過程。HashSet 中的 iterator() 方法如下,
// HashSet
public Iterator<E> iterator() {
return map.keySet[()].iterator();
}
// HashMap
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
// HashMap
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
// 真正實現 iterator() 方法的地方
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
// Android-changed: Detect changes to modCount early.
for (int i = 0; (i < tab.length && modCount == mc); ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
// HashMap
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table; // table 是保存 HashMap 所有數據的 Hash 數組
current = next = null; // current 當前遍歷到的元素,next 下一個需要被遍歷的元素
index = 0; // 初始化遍歷位置
if (t != null && size > 0) { // advance 推進,推進 index 到 Hash 數組中第一個不為 null 的位置,因為 Hash 表中任意位置都可能為 null
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) // 是否存在並發操作的判斷,同 List
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) { // 為 current 賦值,並且定位下一個需要被遍歷的元素
do {} while (index < t.length && (next = t[index++]) == null); // 如果當前單鏈表遍歷結束,則需要在 Hash 表中繼續定位
}
// 返回當前比那裏到的元素
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;
// 調用 HashMap 的方法移除元素
removeNode(hash(key), key, null, false, false);
// 更新期望的命中數量
expectedModCount = modCount;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
上面是 Set 中叠代器的源碼,Set 的叠代器還是通過其本身實現的過程中使用到的 HashMap 的叠代,HashMap 的叠代也就是對 Hash 表的叠代,叠代到 HashMap 中每個元素後,這個元素的 key 就是 Set 中存儲的元素。由 HashSet 的分析延伸到了 HashMap,對 HashMap 遍歷也就是 HashSet 遍歷使用的方式。
五、總結
到這裏我們就將 Java 中集合和 Map 的遍歷都介紹了,但有一點需要註意的是,List 遍歷時每次調用 iterator() 方法都會創建一個 Iterator 的對象,而 Set 和 Map 遍歷時使用的叠代器是只會初始化一次的,不論是 List 還是 Set 和 Map 在使用時都需要註意多線程同事操作時的並發問題。
五合一平臺源碼搭建下載