定义
一种最简单也最常见的设计模式。它可以让用户透过特定的接口巡访容器中的每一个元素而不用了解底层的实现。让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
结构
- 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
- 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
- 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
- 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。
实例
在Java中我们常用的两个List,ArrayList和LinkList,这里我们给这两个List都放入20000个Item,然后循环遍历所有的Item
使用简单的for循环
public class Main {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 20000; i++) {
arrayList.add(i);
linkedList.add(i);
}
long startTime = System.currentTimeMillis();
System.out.println("start iterator arrayList");
for (int i = 0; i < arrayList.size(); i++) {
arrayList.get(i);
}
long endTime = System.currentTimeMillis();
System.out.println("end iterator arrayList : " + (endTime - startTime));
startTime = System.currentTimeMillis();
System.out.println("start iterator linkedList");
for (int i = 0; i < linkedList.size(); i++) {
linkedList.get(i);
}
endTime = System.currentTimeMillis();
System.out.println("end iterator linkedList : " + (endTime - startTime));
}
}
执行结果
start iterator arrayList
end iterator arrayList : 2
start iterator linkedList
end iterator linkedList : 243
Process finished with exit code 0
这里我们可以看到,由于数据结构的不同,这样简单暴力的遍历方式会有很大的差距。
现在我们使用JDK为各个List实现的迭代器来遍历列表。
public class Main {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 20000; i++) {
arrayList.add(i);
linkedList.add(i);
}
long startTime = System.currentTimeMillis();
System.out.println("start iterator arrayList");
Iterator<Integer> arrayListIt = arrayList.iterator();
while (arrayListIt.hasNext()) {
arrayListIt.next();
}
long endTime = System.currentTimeMillis();
System.out.println("end iterator arrayList : " + (endTime - startTime));
startTime = System.currentTimeMillis();
System.out.println("start iterator linkedList");
Iterator<Integer> linkedListIt = linkedList.iterator();
while (linkedListIt.hasNext()) {
linkedListIt.next();
}
endTime = System.currentTimeMillis();
System.out.println("end iterator linkedList : " + (endTime - startTime));
}
}
执行结果
start iterator arrayList
end iterator arrayList : 4
start iterator linkedList
end iterator linkedList : 6
Process finished with exit code 0
我们可以看到,结果的差距就非常的小了,2毫秒的差距,与之前100多倍的差距已经是可以忽略不计的差距了。
对于数组列表ArrayList它是连续内存实现的,所以在for循环来遍历的时候只要知道Index就知道目标对象的指针,所以可以很快的遍历出来。而LinkedList是链表,是散列在内存的,得找到链表头,然后再一个一个的遍历才能找到目标对象,所以对于遍历例子中20000个对象,每次都得从头开始遍历下去,如果没有迭代器,那遍历更大的数据就会是场灾难了。
Java的List的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());
}
}
ArrayList Concrete Iterators的实现类
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;
// prevent creating a synthetic constructor
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
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
适用场景
- 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。
- 使用该模式可以减少程序中重复的遍历代码。
- 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。
优点
- 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
- 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
- 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
缺点
- 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
- 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。