"遍历不是问题,暴露结构才是。"
在现代软件系统中,数据结构日益复杂:嵌套 JSON、图数据库、分页 API、实时事件流......若客户端直接依赖底层存储形式(数组索引、链表指针、SQL 游标),将导致:
- 脆弱性:一旦内部实现变更,所有调用方崩溃;
- 安全风险:外部可篡改内部状态(如跳过校验直接修改节点);
- 复用障碍:无法统一处理不同来源的数据(内存 vs 网络 vs 文件)。
迭代器模式(Iterator Pattern) 的本质,是通过抽象协议隔离"数据持有者"与"数据消费者" ,让遍历行为成为一种受控的服务,而非裸露的权限。
本文将从 语言规范 → 框架实现 → 架构演进 → 反模式识别 四个层次,彻底讲透这一模式的工业级应用。
🔍 第一章:重新定义迭代器 ------ 不只是 next() 和 hasNext()
GoF 的原始定义已不足以描述现代迭代器。真正的迭代器应具备以下能力:
| 能力 | 说明 | 示例 |
|---|---|---|
| 顺序访问 | 支持线性或非线性遍历 | 树的中序、图的 BFS |
| 状态隔离 | 多个迭代器互不干扰 | 同时遍历同一列表两次 |
| 延迟计算 | 按需生成元素(惰性求值) | 无限斐波那契数列 |
| 异常安全 | 遍历中断后资源可释放 | 数据库游标自动关闭 |
| 协议兼容 | 遵循语言标准协议 | ES6 [Symbol.iterator] |
💡 关键认知升级 :
迭代器不是"遍历工具",而是数据访问的契约接口。它定义了"如何安全地消费数据",而非"如何存储数据"。
⚙️ 第二章:JavaScript 迭代器协议深度解析(V8 引擎视角)
ES6 的迭代器协议由两部分组成:
1. 可迭代协议(Iterable Protocol)
对象必须实现 @@iterator 方法(即 [Symbol.iterator]),返回一个迭代器。
2. 迭代器协议(Iterator Protocol)
迭代器对象必须提供 next() 方法,返回 { value, done }。
但仅此不够。高质量实现需考虑:
✅ 正确实现:支持多次遍历
javascript
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
// 每次调用返回新迭代器(关键!)
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const r = new Range(1, 3);
console.log([...r]); // [1,2,3]
console.log([...r]); // [1,2,3] ← 仍有效!
❌ 错误实现:状态共享导致第二次遍历失败
javascript
// 反模式:迭代器状态绑定到聚合对象
class BadRange {
constructor(start, end) {
this.current = start;
this.end = end;
}
[Symbol.iterator]() {
return this; // 返回自身 → 状态污染
}
next() {
if (this.current <= this.end) {
return { value: this.current++, done: false };
}
return { done: true };
}
}
📌 最佳实践 :
[Symbol.iterator]()应返回全新迭代器实例,确保可重入。
☕ 第三章:Java 迭代器的工业级设计 ------ Fail-Fast 与并发控制
Java 的 Iterator 不仅是接口,更是一套并发安全策略:
核心机制:modCount 与 expectedModCount
java
// ArrayList.Itr 内部类片段
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素索引
int lastRet = -1; // 上次返回的元素索引
int expectedModCount = modCount; // 快照
public E next() {
checkForComodification(); // 关键检查
// ... 返回元素
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
并发场景对比
| 集合类型 | 迭代器类型 | 行为 | 适用场景 |
|---|---|---|---|
ArrayList |
Fail-Fast | 修改即抛异常 | 单线程调试 |
CopyOnWriteArrayList |
Fail-Safe(快照) | 遍历副本,不受修改影响 | 读多写少 |
ConcurrentHashMap |
Weakly Consistent | 可能看不到最新修改,但不会崩溃 | 高并发 |
🔥 架构启示:迭代器语义直接影响系统容错能力。选择集合类型前,先明确遍历一致性需求。
🌐 第四章:真实世界中的迭代器 ------ 超越内存集合
迭代器的价值在跨边界数据访问中尤为突出:
场景 1:数据库游标(Cursor)
sql
-- PostgreSQL 游标
DECLARE user_cursor CURSOR FOR SELECT * FROM users;
FETCH 100 FROM user_cursor;
- 客户端无需加载全表;
- 服务端维护遍历状态;
- 自动资源回收(超时关闭)。
场景 2:GraphQL 分页
graphql
query {
users(first: 10, after: "cursor_xyz") {
edges { node { id name } }
pageInfo { hasNextPage endCursor }
}
}
endCursor本质是迭代器状态的序列化;- 支持无限滚动、断点续传。
场景 3:文件流逐行读取(Node.js)
javascript
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('huge.log')
});
rl.on('line', (line) => {
// 每行作为"next()"结果
});
- 内存占用恒定 O(1);
- 适用于 GB 级日志处理。
✅ 共同点 :数据生产者控制节奏,消费者被动接收,解耦时空依赖。
🔄 第五章:迭代器 vs 生成器 vs 流 ------ 概念辨析
| 特性 | 迭代器(Iterator) | 生成器(Generator) | 流(Stream) |
|---|---|---|---|
| 本质 | 接口协议 | 函数语法糖 | 数据处理管道 |
| 状态管理 | 外部对象维护 | 函数挂起(yield) | 内部队列/背压 |
| 组合能力 | 需手动包装 | 天然支持委托(yield*) | 链式操作(map/filter) |
| 语言支持 | Java/JS/Python | JS/Python | Java/RxJS |
| 典型用途 | 集合遍历 | 惰性序列生成 | 响应式数据处理 |
关系图谱:
迭代器(协议)
↑ 实现方式
生成器(语法) → 可返回迭代器
↑ 组合增强
流(框架) → 基于迭代器构建
💡 选型建议:
- 需要自定义集合? → 实现迭代器协议;
- 需要生成无限序列? → 用生成器;
- 需要声明式数据转换? → 用 Stream/RxJS。
🛠️ 第六章:高性能迭代器设计 ------ 避免常见陷阱
陷阱 1:在 next() 中做重型计算
javascript
// 反模式:每次 next 都排序
next() {
const sorted = this.data.sort(); // O(n log n) × n → O(n² log n)
return sorted[this.index++];
}
✅ 修复:预计算或懒初始化。
陷阱 2:忽略资源清理
java
public class FileLineIterator implements Iterator<String> {
private BufferedReader reader;
public String next() { /* 读取一行 */ }
// 忘记 close() → 文件句柄泄漏
}
✅ 修复 :实现 AutoCloseable,或使用 try-with-resources。
陷阱 3:并发修改未同步
java
// 多线程调用 next() 可能重复或跳过元素
public E next() {
return items[index++]; // 非原子操作
}
✅ 修复:加锁或使用线程安全集合。
🧪 第七章:测试迭代器的正确性 ------ 边界用例清单
高质量迭代器必须通过以下测试:
java
@Test
void testIteratorContract() {
MyCollection<Integer> coll = new MyCollection<>(1, 2, 3);
Iterator<Integer> it = coll.iterator();
// 1. 初始状态
assertTrue(it.hasNext());
// 2. 顺序正确
assertEquals(1, it.next());
assertEquals(2, it.next());
assertEquals(3, it.next());
// 3. 结束后不可再 next()
assertFalse(it.hasNext());
assertThrows(NoSuchElementException.class, it::next);
// 4. 可重入(新迭代器)
Iterator<Integer> it2 = coll.iterator();
assertEquals(1, it2.next());
// 5. 并发安全(若承诺)
// ... 多线程测试
}
📌 黄金法则:迭代器的行为应像"只读光标",不可变、可预测、可重放。
🤖 第八章:前端框架中的迭代器思维 ------ React Suspense 与流式 SSR
React 18 的 流式服务端渲染(Streaming SSR) 本质是迭代器思想的延伸:
jsx
// App.jsx
<Suspense fallback="Loading...">
<Comments />
</Suspense>
- 服务端按组件边界"yield" HTML 片段;
- 客户端逐步 hydrate;
- 用户无需等待整页加载。
这相当于:
- 聚合对象:React 应用树;
- 迭代器:ReactDOMServer.renderToReadableStream();
- next():每个 Suspense boundary 的输出。
✨ 架构升华:迭代器模式从"数据遍历"扩展到"UI 渲染流程控制"。
⚖️ 第九章:何时不该用迭代器?
迭代器并非万能。以下场景应避免:
| 场景 | 原因 | 替代方案 |
|---|---|---|
| 需要随机访问 | 迭代器只能顺序前进 | 直接索引(数组) |
| 频繁中间插入/删除 | 迭代器可能失效 | 使用支持游标的数据库 |
| 性能极度敏感 | 方法调用开销 | 手写 for 循环(如游戏引擎) |
| 数据量极小(<10) | 过度设计 | 直接遍历 |
🎯 决策树 :
数据是否复杂?→ 是 → 是否需统一访问?→ 是 → 用迭代器
否则,优先简单方案。
🧩 第十章:与组合模式协同 ------ 遍历树形结构
在组合模式中,迭代器可统一叶节点与容器节点的访问:
java
interface FileSystemNode {
String getName();
Iterator<FileSystemNode> depthFirstIterator();
}
class Directory implements FileSystemNode {
private List<FileSystemNode> children = new ArrayList<>();
public Iterator<FileSystemNode> depthFirstIterator() {
return new DFSIterator(this);
}
private static class DFSIterator implements Iterator<FileSystemNode> {
private Stack<FileSystemNode> stack = new Stack<>();
DFSIterator(Directory root) {
stack.push(root);
}
public boolean hasNext() {
return !stack.isEmpty();
}
public FileSystemNode next() {
FileSystemNode current = stack.pop();
if (current instanceof Directory) {
// 将子节点逆序压栈(保证正序弹出)
List<FileSystemNode> children = ((Directory) current).getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
}
return current;
}
}
}
客户端代码完全 unaware 是否在遍历文件或目录。
🔒 第十一章:安全迭代器 ------ 防御性编程实践
对外暴露的迭代器应防止恶意使用:
1. 禁止结构修改
java
// 返回不可变视图的迭代器
public Iterator<T> iterator() {
return Collections.unmodifiableList(internalList).iterator();
}
2. 防御性拷贝(高安全场景)
java
public Iterator<T> iterator() {
return new ArrayList<>(internalList).iterator(); // 快照
}
3. 权限控制
javascript
// 仅授权用户可遍历
[Symbol.iterator]() {
if (!currentUser.hasPermission('READ')) {
throw new Error('Access denied');
}
// ...
}
🛡️ 原则:迭代器是数据出口,必须像 API 网关一样做鉴权与限流。
📈 第十二章:性能基准测试 ------ 迭代器 vs 原生循环
在 Node.js 中测试 100 万次遍历:
| 方式 | 时间(ms) | 内存(MB) |
|---|---|---|
for (let i=0; ...) |
12 | 40 |
for...of(原生数组) |
18 | 40 |
| 自定义迭代器 | 45 | 42 |
| Generator 函数 | 60 | 45 |
结论:
- 原生循环最快,适合性能关键路径;
- 迭代器开销可控(<3x),换取封装性值得;
- 避免在热路径用 Generator。
📊 建议:95% 场景用语言内置遍历,5% 自定义结构才需手写迭代器。
🧭 第十三章:未来演进 ------ 异步迭代器与 Web Streams
ES2018 引入 异步迭代器(AsyncIterator),用于处理流式异步数据:
javascript
const fetchStream = async function* () {
let page = 1;
while (true) {
const res = await fetch(`/api/data?page=\${page}`);
const data = await res.json();
if (data.length === 0) return;
for (const item of data) yield item;
page++;
}
};
// 使用
for await (const item of fetchStream()) {
process(item);
}
这与 Web Streams API 深度集成:
javascript
const stream = new ReadableStream({ /* ... */ });
for await (const chunk of stream) { /* ... */ }
🌍 趋势:迭代器正从"内存遍历"走向"时空解耦的数据管道"。
📌 第十四章:终极最佳实践清单
- 遵循语言协议 :JS 用
[Symbol.iterator],Java 实现Iterable; - 保证可重入 :每次
iterator()返回新实例; - 明确一致性模型:Fail-Fast / Fail-Safe / Weakly Consistent;
- 资源必须释放 :实现
close()或return()(Generator); - 禁止在遍历时修改结构 (除非提供安全方法如
remove()); - 对外暴露只读视图:防御性拷贝或不可变包装;
- 文档化行为:是否支持 remove?是否线程安全?
- 性能敏感场景回退到原生循环。
🎯 结语:迭代器是数据主权的守门人
在数据爆炸的时代,谁控制了遍历,谁就控制了数据的解释权。
迭代器模式远不止于"如何循环",它是一种访问控制哲学:
- 对内,隐藏实现细节,保护数据完整性;
- 对外,提供标准化契约,促进生态协作。
从 Java 集合到 GraphQL,从数据库游标到 React Suspense,迭代器始终在幕后守护着软件世界的秩序与安全。
下次设计 API 时,请自问:
"我是在暴露数据,还是在提供服务?"