彻底搞懂「迭代器 Iterator」与「游标 Cursor」—— 同源异路的遍历设计
在日常开发与源码阅读中,迭代器(Iterator)和游标(Cursor)是两个高频出现的概念。很多人会疑惑:它们到底是不是一回事?有什么区别?又为什么经常被放在一起比较?
本文基于最本质的设计思想,从相同点、区别、代码示例、应用场景完整讲透,帮你一次彻底理解。
一、先破误区:它们本质是同一个设计思想
很多资料把迭代器和游标讲得完全割裂,但从设计模式角度看:
迭代器模式 = 游标模式(Cursor Pattern)
二者核心思想完全同源,只是应用领域、实现方式、生命周期不同。
可以直接记一句最本质的话:
迭代器是内存集合的游标;游标是数据库结果集的迭代器。
二、核心相同点:为什么说它们本质一样?
这是理解底层设计最重要的部分,也是很多人忽略的关键点。
1. 都是「位置指针」,不存储数据
无论迭代器还是游标,本身不保存任何数据,只做一件事:
标记当前遍历到哪个位置。
- 迭代器:指向集合中某元素的位置
- 游标:指向结果集中某一行的位置
它们都是轻量位置指示器。
2. 都是「惰性遍历」,不一次性加载
两者都遵循按需读取:
- 不取不加载
- 读一个,移动一次,再读下一个
- 不会把整个集合/结果集全部载入内存
这是它们能处理大数据量、流式数据的根本原因。
3. 都提供统一的遍历接口
行为高度一致,核心操作几乎一模一样:
- 判断是否还有元素(
hasNext/FETCH STATUS) - 移动到下一个(
next/FETCH NEXT) - 获取当前元素(
current/当前行)
接口抽象逻辑完全相同。
4. 都隐藏底层结构,对外统一访问
- 迭代器:不管底层是数组、链表、树、哈希表,遍历方式一致
- 游标:不管数据库存储结构是 B+树、堆表,取行方式一致
面向接口,不暴露内部实现,这是封装的核心体现。
5. 都支持单向顺序遍历(基础形态)
最朴素的迭代器与游标,都是从前到后、顺序访问,这是它们最原始的设计形态。
三、代码示例:直观感受二者逻辑高度一致
示例1:Java 迭代器(内存集合)
importjava.util.Arrays;importjava.util.Iterator;importjava.util.List;publicclassIteratorDemo{publicstaticvoidmain(String[]args){List<String>list=Arrays.asList("张三","李四","王五");// 获取迭代器:相当于打开游标Iterator<String>it=list.iterator();// hasNext:判断是否还有数据while(it.hasNext()){// next:移动指针并获取当前元素Stringname=it.next();System.out.println("当前元素:"+name);}// 迭代器无需手动关闭,GC 自动回收}}核心动作:hasNext()→ 判断是否有下一个next()→ 移动指针 + 取数据
示例2:MySQL 存储过程游标(数据库)
-- 定义游标,逐行遍历用户表DELIMITER//CREATEPROCEDUREcursorDemo()BEGINDECLAREu_nameVARCHAR(50);DECLAREdoneINTDEFAULT0;-- 1. 声明游标(类似获取 iterator())DECLAREuser_curCURSORFORSELECTnameFROMuserLIMIT3;-- 2. 遍历结束标志(类似 !hasNext())DECLARECONTINUEHANDLERFORNOTFOUNDSETdone=1;-- 3. 打开游标(初始化)OPENuser_cur;-- 循环遍历read_loop:LOOP-- 4. 获取当前行(类似 next())FETCHuser_curINTOu_name;IFdone=1THENLEAVEread_loop;ENDIF;SELECTCONCAT('当前行:',u_name)ASinfo;ENDLOOP;-- 5. 必须关闭游标,释放资源CLOSEuser_cur;END//DELIMITER;CALLcursorDemo();游标核心动作:OPEN→ 初始化FETCH→ 移动指针 + 取当前行CLOSE→ 释放资源
可以清晰看到:
迭代器与游标代码逻辑几乎一一对应。
四、关键区别:相同灵魂,不同肉身
虽然思想同源,但应用场景决定了它们的差异巨大。
1. 应用领域不同
- 迭代器:编程语言层面,内存集合遍历
List、Set、Map、Stream、生成器等 - 游标:数据库层面,结果集逐行控制
SQL 查询返回的 ResultSet,服务端状态对象
2. 轻重级别不同
- 迭代器:极轻量
只是一个对象+位置索引,无资源占用 - 游标:重量级
占用数据库连接、服务端内存、事务上下文、锁资源
3. 生命周期与管理
- 迭代器:用完即废,无需手动关闭
- 游标:必须显式 OPEN / CLOSE / DEALLOCATE
忘记关闭会导致资源泄漏、连接占满、锁不释放
4. 功能能力不同
- 迭代器:以只读、单向遍历为主
大部分标准迭代器不支持回退、修改 - 游标:功能更强,可控制
可前后滚动、定位、修改当前行、删除当前行
5. 数据来源不同
- 迭代器:内存数据
- 游标:磁盘/网络数据
五、极简总结表
| 对比维度 | 迭代器 Iterator | 游标 Cursor |
|---|---|---|
| 本质 | 位置指针 | 位置指针 |
| 设计模式 | 迭代器模式 | 游标模式(同一模式) |
| 遍历方式 | 惰性、逐个、流式 | 惰性、逐个、流式 |
| 存储位置 | 内存 | 数据库服务器 |
| 重量 | 轻量 | 重量级 |
| 资源 | 无额外资源 | 占连接、内存、锁 |
| 关闭 | 自动失效 | 必须手动关闭 |
| 功能 | 单向遍历为主 | 可滚动、可修改、可定位 |
| 适用场景 | 语言集合遍历 | 数据库大结果集逐行处理 |
六、各自的应用场景与最佳实践
迭代器的典型场景
- 遍历内存中的集合(列表、字典、集合)
- 实现生成器或懒加载列表(例如读取大文件的行)
- 流水线式数据处理(函数式编程中的 map、filter)
- 为自定义数据结构提供标准遍历接口
✅ 优先使用迭代器,除非你要处理数据库中的数据。
游标的典型场景
游标在数据库开发中名声不太好——因为它通常比基于集合的操作(如UPDATE … WHERE)慢一个数量级。但某些场景下你不得不使用它:
- 需要逐行执行复杂业务逻辑,无法用一条 SQL 完成
- 调用每行数据都不同的存储过程或外部 API
- 实现分批提交以避免长事务锁定
- 处理分页(但通常用 LIMIT/OFFSET 或键集分页更优)
- 某些高级需求:可滚动游标、敏感游标(反映他人提交的更改)
❌不要为了“看起来像代码”而用游标代替批量 SQL。
七、一句话终极理解(最精髓)
迭代器与游标,本质完全相同——都是「带位置的遍历抽象」。
只是一个运行在内存语言层,一个运行在数据库服务层。
懂了这句话,你就真正理解了遍历设计的核心:
位置 + 移动 + 取值 + 隐藏底层结构。
