文章目錄
一、iterator
接口介紹
iterator
接口,也是集合大家庭中的一員。和其他的Map
和Collection
接口不同,iterator
主要是爲了方便遍歷集合中的所有元素,用於迭代訪問集合中的元素,相當於定義了遍歷元素的規範,而另外的Map
和Collection
接口主要是定義了存儲元素的規範。
還記得麼?之前說的iterable
接口,有一個方法就是叫iterator()
,也是返回iterator
對象。
迭代:不斷訪問集合中元素的方式,取元素之前先判斷是否有元素,有則取出來,沒有則結束,不斷循環這個過程,直到遍歷完裏面所有的元素。
接口定義的方法如下:
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循環索引的方式遍歷,刪除掉一個元素之後,集合的元素個數已經變化,很容易出錯。例如
for(int i=0;i<collection.size();i++){
if(i==2){
collection.remove(i);
}
}
而iterator
的remove()
方法則不會出錯,因爲通過調用hasNext()
和next()
方法,對指針控制已經處理得比較完善。
二、爲什麼需要iterator接口
首先,我們知道iterator
接口是爲了定義遍歷集合的規範,也是一種抽象,把在不同集合的遍歷方式抽象出來,這樣遍歷的時候,就不需要知道不同集合的內部結構。
爲什麼需要抽象?
假設沒有iterator
接口,我們知道,遍歷的時候只能通過索引,比如
for(int i=0;i<array.size();i++){
T item = array[i];
}
這樣一來,耦合程度比較高,如果使用的數據結構變了,就要換一種寫法,不利於維護已有的代碼。如果沒有iterator
,那麼客戶端需要維護指針,相當於下放了權限,會造成一定程度的混亂。抽象則是把遍歷功能抽取出來,交給iterator
處理,客戶端處理集合的時候,交給更“專業”的它,it do it well.
三、iterator接口相關接口
3.1 ListIterator
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中的元素修改有可能導致數組越界。
3.2 SpitIterator
準確地來說,SpitIterator
和Iterator
並沒有什麼關係,只是兩個功能上有類似。SpitIterator
主要是定義類將集合分割成多個集合,方便並行計算。
3.2.1 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() 一個一個元素進行遍歷
- forEachRemaining() 順序地分塊遍歷
- trySplit()進行分區形成另外的 Spliterator,使用在並行操作中,分出來的是前面一半,就是不斷把前面一部分分出來
結果如下:
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()之後,可以用於多線程遍歷。理想的時候,可以平均分成兩半,有利於並行計算,但是不是一定平分的。
3.2.2 SpitIterator裏面哪些特徵常量有什麼用呢?
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
只是一個接口,相當於一個規範,所有的子類或者繼承類實現的時候理論上應該遵守,但是不一樣的繼承類/子類會有不一樣的實現。
4.1 iterator在ArrayList的實現
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
異常。
4.2 iterator在HashMap的實現
首先,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,就是校驗這個迭代過程中是否被修改,如果修改了,則會拋出異常。