go无内置iterator接口,遍历仅靠range关键字,依赖对数组、切片、map、channel、字符串的硬编码支持;自定义类型需用yield回调或channel实现惰性遍历。

Go 里没有内置的 Iterator 接口,range 是唯一通用遍历机制
Go 不像 Java 或 C# 那样提供 Iterator 接口或 next()/hasNext() 模式。它的遍历能力全部收束在 range 关键字上,而 range 背后依赖的是语言对几种底层类型的硬编码支持:数组、切片、map、channel、字符串。你无法为自定义类型“实现 Iterator 接口”来让 range 自动工作。
这意味着:想用 range 遍历树、链表、图、数据库游标这类结构?不行。必须显式暴露方法,或者封装成 channel。
- 常见错误现象:
cannot range over myTree (type *TreeNode) - 使用场景:需要按需遍历嵌套结构(比如二叉树中序遍历)、避免一次性加载全部数据(如分页查询结果流式消费)
- 性能影响:直接返回
[]T会强制全量构建切片,内存和 GC 压力大;用 channel 或回调可实现惰性求值
用 func(yield func(T) bool) 替代传统迭代器接口
这是 Go 社区更惯用、更轻量、也更符合 Go “少抽象、重组合” 哲学的做法——不定义接口,而是把控制权交给调用方,通过传入一个 yield 回调函数来逐个处理元素。
它规避了 channel 的 goroutine 开销和关闭管理问题,也比手写状态机(如维护 current 指针 + Next() 方法)更简洁安全。
立即学习“go语言免费学习笔记(深入)”;
- 示例:二叉树中序遍历
func (t *TreeNode) Walk(yield func(int) bool) {<br> if t == nil {<br> return<br> }<br> if !t.Left.Walk(yield) { return }<br> if !yield(t.Val) { return }<br> t.Right.Walk(yield)<br>}- 为什么这样做:yield 返回
bool支持提前终止(类似break),调用方无需关心内部状态,也不用担心漏关 channel 或 panic on closed channel - 容易踩的坑:yield 函数里做耗时操作?会阻塞整个遍历;yield 中 panic?会向上冒泡——需由调用方决定是否 recover
用 chan T 实现协程版“迭代器”,但要管好生命周期
当你确实需要类 iterator 的“拉取式”行为(比如配合 for v := range iter.Chan()),用 channel 是可行路径,但它不是零成本抽象。
channel 版本本质是启动一个 goroutine 执行遍历逻辑,并把元素发到 channel;调用方从 channel 接收。这带来两个关键约束:goroutine 必须可控退出,channel 必须被关闭。
- 常见错误现象:
fatal error: all goroutines are asleep - deadlock(忘记关闭 channel 或没消费完就退出) - 使用场景:需要与
select配合做超时控制、多路复用(如合并多个数据源) - 参数差异:建议提供带
context.Context的变体,例如Chan(ctx context.Context) ,便于外部取消 - 性能影响:每个迭代器实例都起 goroutine,高频创建易引发调度压力;小数据量下不如 callback 模式高效
别为了“模式”而造接口:标准库里 io.Reader 和 sql.Rows 已是事实迭代器
Go 标准库其实早有“迭代器思维”的落地,只是不叫这个名字:io.Reader.Read(p []byte) 是按块拉取,sql.Rows.Next() + sql.Rows.Scan() 是按行拉取。它们共同点是:状态内含、单向、需手动控制生命周期。
如果你的类型语义接近“流式数据源”,直接模仿这些设计比硬套 GOF 迭代器更自然、更易被其他 Go 程序员理解。
- 容易踩的坑:给结构体加
Next() bool和Value() T方法时,忘记处理并发访问(非线程安全),或没明确文档说明“调用Next()后才能读Value()” - 兼容性影响:一旦暴露
Next()方法,后续很难改成无状态函数式 API;而Walk(yield)或Chan(ctx)更易演进
真正难的从来不是怎么写一个 Next(),而是判断这个遍历过程要不要支持中断、是否允许并发读、元素生成代价高不高——这些决定了 yield、channel、还是 stateful method 更合适。选错抽象,后面每加一个新遍历需求,都要补一堆适配胶水代码。










