一、 Iterator 是什麼?
1、迭代器模式
迭代器模式(Iterator Pattern
)是一種非常常見的設計模式,這種模式用於順序訪問集合對象的元素,而不需要知道集合對象內部的實現方式。
所以,迭代器模式的優點就是:簡化了聚合類。無論是增加新的聚合類還是增加迭代器類都會很方便,無須修改原有的代碼。
它的優點也導致了它的缺點:由於迭代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類時也需要對應增加新的迭代器類,耦合度很高,這在一定程度上增加了系統的複雜性。
2、Iterator 接口
在 Java
中,提供了一個迭代器接口 Iterator
,把在集合對象中元素之間遍歷的工作交給迭代器,而不是集合對象本身,迭代器爲遍歷不同的集合對象提供一個統一的接口。這就是 Java
集合框架中 Iterable
接口位於框架結構最頂層的原因。這其實也就是面向對象的思想。
二、Iterator 的使用
下面我們先看看 Iterator
是如何使用的。
1、Iterator 中的方法
先從 Iterator
接口的源碼來分析一下:
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());
}
}
可以看到,Iterator
接口提供的方法很少,也比較簡單:
boolean hasNext()
:如果迭代中還有更多的元素,則返回true
,否則返回false
;E next()
:返回迭代中的下一個元素;default void remove()
:從集合中移除此迭代器返回的最後一個元素,它是一個可選的操作;default void forEachRemaining(Consumer action)
:對每個剩餘元素執行給定的操作,直到所有元素都已處理完畢或該操作引發異常。
2、Iterator 基本示例
瞭解了 Iterator
提供的方法,下面我們來使用一下:
import java.util.Iterator;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> names = new LinkedList<String>();
names.add("Deepspace");
names.add("chenxingxing");
Iterator<String> namesIterator = names.iterator();
while (namesIterator.hasNext()) {
System.out.println(namesIterator.next());
}
}
}
輸出結果爲:
Deepspace
chenxingxing
其實,我們也可以使用 foreach
方法來遍歷集合:
for (String name : names) {
System.out.println(name);
}
我們再試試 remove
方法:
public class Main {
public static void main(String[] args) {
List<String> names = new LinkedList<String>();
names.add("E-1");
names.add("E-2");
names.add("E-3");
names.add("E-n");
Iterator<String> namesIterator = names.iterator();
while (namesIterator.hasNext()) {
String next = namesIterator.next();
System.out.println(next);
if ("E-3".equals(next)) {
namesIterator.remove();
}
}
System.out.println(names); // [E-1, E-2, E-n]
}
}
可以看到 E-3
這個元素被移除掉了。
再看看 forEachRemaining
方法(注意,該方法是沒有返回值的):
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new LinkedList<String>();
names.add("E-1");
names.add("E-2");
names.add("E-3");
names.add("E-n");
List<String> targetList = new LinkedList<String>();
Iterator<String> namesIterator = names.iterator();
while (namesIterator.hasNext()) {
String next = namesIterator.next();
if ("E-3".equals(next)) {
namesIterator.remove();
namesIterator.forEachRemaining(targetList::add); // 這裏用到了 Java8 裏的新語法
}
}
System.out.println(targetList); // [E-3, E-n]
}
}
三、Iterator 內部是如何工作的?
下面我們來了解下 Java
迭代器及其方法是如何在內部工作的。以 LinkedList
對象爲例。
創建一個 List
集合,並插入幾條數據:
List<String> names = new LinkedList<String>();
names.add("E-1");
names.add("E-2");
names.add("E-3");
names.add("E-n");
現在在 List
對象上創建一個迭代器對象:
Iterator<String> namesIterator = names.iterator();
可以用下面的圖來表示 nameIterator
:
這裏,Iterator
的 Cursor
(光標)指向 List
的第一個元素之前。
下面我們再運行下面兩行代碼:
namesIterator.hasNext();
namesIterator.next();
這個時候,Iterator
的 Cursor
指向 List
中的第一個元素,如下圖所示:
我們再運行一下剛纔的兩行代碼:
namesIterator.hasNext();
namesIterator.next();
Iterator
的 Cursor
會指向 List
中的第二個元素:
以此類推,重複執行此過程,可將 Iterator
的 Cursor
指向 List
中的最後一個元素。
當讀取最後一個元素後,如果繼續運行下面的代碼片段,它將返回 false
。
從上面的描述可以看出,Java
迭代器只支持如下圖所示的前進方向迭代。
所以在一些地方迭代器也稱爲單向光標。
四、Iterator 的優缺點
從前面的講解中,我們可以知道 Iterator 有以下優點:
- 可以在任何集合類中使用它,對於集合
API
是通用的; - 它支持
READ
和REMOVE
操作; - 它的方法名稱簡單且易於使用。
但是它也有一些缺陷和限制:
- 在
CRUD
操作中,它不支持CREATE
和UPDATE
操作; - 它只支持連續迭代(正向迭代),迭代時不支持迭代元素並行(與
Spliterator
相比,我們待會講到)。
什麼是並行呢?
並行指的是以某種方式利用多核 CPU
單元進行編程。也就是說,要完成一項任務,可以將其分解爲可以並行處理的單獨的子任務,然後彙總所有已處理單元的結果以完成原始工作
五、ListIterator
Java
中也提供了一個 ListIterator
接口,該接口繼承了 Iterator
接口,只對 List
實現的類有用。我們看看源碼:
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
可以看到,ListIterator
接口新增了一些方法,可以在迭代的時候進行 UPDATE
和 ADD
的操作,所以 ListIterator
接口就完全支持 CURD
操作了。
同時,我們也可以從 newIndex
和 previous
方法中發現,ListIterator
是一個雙向迭代器,支持正向迭代和反向迭代。
這裏要注意,ListIterator
沒有當前元素;它的光標位置始終位於 previous()
返回的元素和 next()
的返回的元素之間。
1、ListIterator 基本示例
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List<String> names = new LinkedList<String>();
names.add("E-1");
names.add("E-2");
names.add("E-3");
names.add("E-n");
ListIterator<String> namesIterator = names.listIterator();
while (namesIterator.hasNext()) {
String next = namesIterator.next();
System.out.println(next);
if ("E-3".equals(next)) {
namesIterator.add("E-4");
}
if ("E-n".equals(next)) {
namesIterator.set("E-5");
}
}
System.out.println("\nFor Loop");
for (String name : names) {
System.out.println(name);
}
}
}
輸出結果爲:
E-1
E-2
E-3
E-n
For Loop
E-1
E-2
E-3
E-4
E-5
上面的代碼演示了 UPDATE
和 ADD
操作。下面我們再看看,ListIterator
的方法如何執行前向和後向迭代。
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List<String> names = new LinkedList<String>();
names.add("E-1");
names.add("E-2");
names.add("E-3");
names.add("E-n");
ListIterator<String> namesIterator = names.listIterator();
System.out.println("Forward Direction Iteration:");
while(namesIterator.hasNext()){
System.out.println(namesIterator.next());
}
System.out.println("Backward Direction Iteration:");
while(namesIterator.hasPrevious()){
System.out.println(namesIterator.previous());
}
}
}
輸出結果爲:
Forward Direction Iteration:
E-1
E-2
E-3
E-n
Backward Direction Iteration:
E-n
E-3
E-2
E-1
2、ListIterator 的侷限性
與 Iterator
相比,ListIterator
有許多優勢。 但是它仍然存在一些侷限性:
- 它不適用於整個集合
API
,只對List
實現的類有用,而Iterator
支持所有的集合類型; - 依然不支持元素的並行迭代;
六、Spliterator
最早的時候,那個時候 CPU
還是單核時代,Java
提供順序遍歷迭代器 Iterator
時,可以滿足需求;但到了多核時代下,順序遍歷已經不能滿足需求了,如何把多個任務分配到不同核上並行執行,最大發揮多核的能力,所以 Spliterator
就誕生了。
Spliterator
是 Java8
中新增的一個 API
,除了支持順序遍歷之外,還支持高效的並行遍歷。
由於是
Java8
中新增的API
,所以在介紹它的使用上,讀者需要具備Stream
和Lambda
表達式的一些知識。
1、Spliterator 中的方法
1).characteristics()
:返回 Spliterator
數據對應的特徵值。根據文檔給出的例子,它返回的是多個特徵碼的按位或的結果:
public int characteristics() {
return ORDERED | SIZED | IMMUTABLE | SUBSIZED;
}
2).hasCharacteristics(int characteristics)
:如果這個 Spliterator
的 .characteristics()
包含所有給定的特徵,則返回 true
;
3).estimateSize()
:在執行前,將要遍歷遇到的元素數量進行估算並返回估計值(long
類型),如果無法返回(包括正無窮、位置或者計算超時等情況)則返回 long.MAX_VALUE
;
4).forEachRemaining(E e)
:在當前線程中,按順序對集合中的每個剩餘元素執行給定操作;
5).getComparator()
:如果該 Spliterator
的源是由 Comparator
排序的,則會將 Comparator
返回;
6).getExactSizeIfKnown()
:如果 estimateSize
的大小已知,則返回 .estimateSize()
方法的返回值,也就是數量的大小,否則返回 -1
;
7).tryAdvance(E e)
:如果存在剩餘元素,則對其執行給定操作,成功則返回 true
,否則返回 false
;
-
注意,該方法和
forEachRemaining
是有區別的,官方給的示例是:public boolean tryAdvance(Consumer<? super T> action) { if (origin < fence) { action.accept((T) array[origin]); origin += 2; return true; } else // cannot advance return false; }
-
從中我們可以看到該方法只執行一次,如果成功就返回
true
否則返回false
,而forEachRemaining
是會循環執行的:public void forEachRemaining(Consumer<? super T> action) { for (; origin < fence; origin += 2) action.accept((T) array[origin]); }
8).trySplit()
:這個方法其實就是負責並行的方法,官方文檔是這樣描述的:
* <p>A Spliterator may also partition off some of its elements (using
* {@link #trySplit}) as another Spliterator, to be used in
* possibly-parallel operations. Operations using a Spliterator that
* cannot split, or does so in a highly imbalanced or inefficient
* manner, are unlikely to benefit from parallelism. Traversal
* and splitting exhaust elements; each Spliterator is useful for only a single
* bulk computation.
理解起來就是,可以使用這個方法對 Spliterator
對象的元素進行切分,用切分出來的部分創建一個新的Spliteraor
對象並返回,以方便進行並行操作。而調用該方法的線程會將返回的 Spliterator
交給另一個新的線程,新的線程又可以繼續分區,這樣就使得程序的執行速度大大提高。
那該分成多少個線程呢?如果線程池中線程數量過多,最終它們會與處理其它任務的線程來競爭稀缺的處理器和內存資源,浪費大量的時間在上下文切換上。反之,如果線程的數目過少,那麼多核處理器的一些核可能就無法充分利用。這個問題在這裏暫不討論,會在併發編程裏講解。
2、Spliterator 的基本示例
下面我們將通過一個例子討論 Spliterator
如何使用並行化更有效地遍歷我們可以分解的 Stream
。
先看看其中的一些方法:
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
List<String> mutants = new ArrayList<>();
mutants.add("Professor X");
mutants.add("Magneto");
mutants.add("Storm");
mutants.add("Jean Grey");
mutants.add("Wolverine");
mutants.add("Mystique");
// Obtain a Stream to the mutants List.
Stream<String> mutantStream = mutants.stream();
// Getting Spliterator object on mutantStream.
Spliterator<String> mutantList = mutantStream.spliterator();
// .estimateSize() method
System.out.println("Estimate size: " + mutantList.estimateSize());
// .getExactSizeIfKnown() method
System.out.println("\nExact size: " + mutantList.getExactSizeIfKnown());
System.out.println("\nContent of List:");
// .forEachRemaining() method
mutantList.forEachRemaining((n) -> System.out.println(n));
}
}
輸出結果爲:
Estimate size: 6
Exact size: 6
Content of List:
Professor X
Magneto
Storm
Jean Grey
Wolverine
Mystique
3、實現並行
再看看使用 trySplit()
方法,實現並行:
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
List<String> mutants = new ArrayList<>();
mutants.add("Professor X");
mutants.add("Magneto");
mutants.add("Storm");
mutants.add("Jean Grey");
mutants.add("Wolverine");
mutants.add("Mystique");
Stream<String> mutantStream = mutants.stream();
// 創建一個新的 Stream.
Spliterator<String> splitList1 = mutantStream.spliterator();
// 調用 .trySplit() 方法,從 splitList1 中拆分一個 splitList2
Spliterator<String> splitList2 = splitList1.trySplit();
// 如果 splitList1 可以被拆分,也就說 splitList2 不爲 null, 那就使用 splitList2.
if (splitList2 != null) {
System.out.println("\nOutput from splitList2:");
splitList2.forEachRemaining((n) -> System.out.println(n));
}
// 這裏用 splitList1
System.out.println("\nOutput from splitList1:");
splitList1.forEachRemaining((n) -> System.out.println(n));
}
}
輸出結果爲:
Output from splitList2:
Professor X
Magneto
Storm
Output from splitList1:
Jean Grey
Wolverine
Mystique
七、Iterable 接口
我們發現,在集合框架的最頂層就是 Iterable
接口,該接口中只有三個方法,源碼如下:
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> var1) {
Objects.requireNonNull(var1);
Iterator var2 = this.iterator();
while(var2.hasNext()) {
Object var3 = var2.next();
var1.accept(var3);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(this.iterator(), 0);
}
}
其中,返回了一個 Iterator
,Spliterator
方法是 Java8
中新增的。
那麼問題就來了,爲什麼集合框架一定要實現 Iterable
接口,而不直接實現 Iterator
接口呢?
通過前面的講解,我們知道 Iterator
接口的核心方法是 next()
和 hasNext()
方法,而這兩個方法是依賴於迭代器的當前迭代位置的。如果 Collection
直接實現 Iterator
接口,那就會導致集合對象中包含當前迭代位置的數據(指針)。
當集合在不同方法間被傳遞時,由於當前迭代位置不可預知,那麼 next()
方法的結果也就會變成不可預知。 除非再爲 Iterator
接口添加一個 reset()
方法,用來重置當前迭代位置。 但是即使這樣做的話,Collection
也只能同時存在一個當前迭代位置。
而集合框架實現了 Iterable
接口,每次調用都會返回一個從頭開始計數的迭代器,這樣多個迭代器是互不干擾的。
所以,基於上面的原因,集合框架實現的是 Iterable
接口,而不是直接實現 Iterator
接口。
八、編寫自定義的 Iterator
有的時候,我們需要要創建一個自定義的 Iterator
接口。
通過 Iterator
接口的源碼,我們知道:要創建自定義 Iterator
,我們最少需要實現 .hasNext()
和 .next()
方法。
CustomList.java
:
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class CustomList<T> implements Iterable<T> {
private List<T> list;
CustomList(List<T> list) {
this.list = list;
}
public Iterator<T> iterator() {
return new EvenIterator<T>();
}
private class EvenIterator<T> implements Iterator<T> {
int size = list.size();
int currentPointer = 0;
public boolean hasNext() {
return (currentPointer < size);
}
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T val = (T) list.get(currentPointer);
currentPointer += 1;
return val;
}
}
}
Student.java
:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "name: " + name + ", age: " + age + ".";
}
}
Main.java
:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student("xiaoming", 22));
list.add(new Student("xiaohgong", 33));
list.add(new Student("xiaobai", 34));
list.add(new Student("xiaohuang", 48));
CustomList<Student> myList = new CustomList<Student>(list);
Iterator<Student> iter = myList.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}
輸出結果爲:
name: xiaoming, age: 22.
name: xiaohgong, age: 33.
name: xiaobai, age: 34.
name: xiaohuang, age: 48.
九、Enumeration 接口
Enumeration
接口也可以用於迭代,其中定義了一些方法,通過這些方法可以枚舉(一次獲得一個)對象集合中的元素。
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
hasMoreElements()
方法用於檢測Enumeration
對象中是否還有元素,有則返回true
,否則返回false
;nextElement()
: 如果Enumeration
對象還有元素,該方法用於獲取下一個元素。
可以看到,Enumeration
接口的作用與 Iterator
接口類似。但是它只提供了遍歷 Vector
和 Hashtable
類型集合元素的枚舉功能,不支持元素的移除操作。
看個例子:
import java.util.Enumeration;
import java.util.Vector;
public class Main {
public static void main(String[] args) {
Enumeration<Integer> company;
Vector<Integer> employees = new Vector<Integer>();
// add values to employees
employees.add(1001);
employees.add(2001);
employees.add(3001);
employees.add(4001);
company = employees.elements();
while (company.hasMoreElements()) {
// get elements using nextElement()
System.out.println("Emp ID = " + company.nextElement());
}
}
}
1、Enumeration 接口和 Iterator 接口的區別
下面我們看看兩者的區別:
1)函數接口不同
Enumeration
只有2
個函數接口;通過Enumeration
,我們只能讀取集合的數據,而不能對數據進行修改;- 而
Iterator
接口中還有其他方法,除了能讀取集合的數據之外,也能對數據進行刪除操作。
2)Iterator
支持 fail-fast
機制,而 Enumeration
不支持
什麼是 fail-fast
機制呢?
fail-fast
機制是 Java
集合中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生 fail-fast
事件。例如:當某一個線程 A
通過 Iterator
去遍歷某集合的過程中,若該集合的內容被其他線程所改變了,那麼線程 A
訪問集合時,就會拋出 ConcurrentModificationException
異常,產生 fail-fast
事件。
Enumeration
是 JDK 1.0
添加的接口。使用到它的函數包括 Vector
、Hashtable
等類,這些類都是 JDK 1.0
中加入的,Enumeration
存在的目的就是爲它們提供遍歷接口。Enumeration
本身並沒有支持同步,而在 Vector
、Hashtable
實現 Enumeration
時,添加了同步。
而 Iterator
是 JDK 1.2
才添加的接口,它也是爲了 HashMap
、ArrayList
等集合提供遍歷接口。 Iterator
是支持 fail-fast
機制的,當多個線程對同一個集合的內容進行操作時,就可能會產生 fail-fast
事件。
所以,使用 Iterator
更加安全。
3)性能方面,對於 Vector
和 Hashtable
類型集合來說,Enumeration
的速度比 Iterator
接口快,同時所需的內存也遠比 Iterator
低。
我們測試一下:
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;
public class IteratorEnumeration {
public static void main(String[] args) {
int val;
Random r = new Random();
Hashtable table = new Hashtable();
for (int i = 0; i < 1000000; i++) {
// 隨機獲取一個[0,100)之間的數字
val = r.nextInt(100);
table.put(String.valueOf(i), val);
}
// 通過Iterator遍歷Hashtable
iterateHashtable(table);
// 通過Enumeration遍歷Hashtable
enumHashtable(table);
}
/*
* 通過Iterator遍歷Hashtable
*/
private static void iterateHashtable(Hashtable table) {
long startTime = System.currentTimeMillis();
Iterator iter = table.entrySet().iterator();
while (iter.hasNext()) {
iter.next();
}
long endTime = System.currentTimeMillis();
countTime(startTime, endTime);
}
/*
* 通過Enumeration遍歷Hashtable
*/
private static void enumHashtable(Hashtable table) {
long startTime = System.currentTimeMillis();
Enumeration enu = table.elements();
while (enu.hasMoreElements()) {
enu.nextElement();
}
long endTime = System.currentTimeMillis();
countTime(startTime, endTime);
}
private static void countTime(long start, long end) {
System.out.println("time: " + (end - start) + "ms");
}
}