下面是一段相对完整的 Iterator 相关源码(为了便于阅读,这里只摘取了关键部分),并进行详细解析:
// Iterator provides a mechanism to traverse nodes in key order within the tree.
type Iterator interface {
// HasNext returns true if there are more nodes to visit during the iteration.
HasNext() bool
// Next returns the next node in the iteration and advances the iterator's position.
Next() (Node, error)
}
//------------------------------------------------------------------------------
// iterator 及其配套数据结构
//------------------------------------------------------------------------------
// state 维护迭代过程的栈,每一层会存储一个 iteratorContext。
type state struct {
items []*iteratorContext
}
// push 在栈顶压入新的 iteratorContext。
func (s *state) push(ctx *iteratorContext) {
s.items = append(s.items, ctx)
}
// current 返回栈顶元素(即当前 iteratorContext)。
func (s *state) current() (*iteratorContext, bool) {
if len(s.items) == 0 {
return nil, false
}
return s.items[len(s.items)-1], true
}
// discard 弹出栈顶元素,表示这一层的遍历已完成。
func (s *state) discard() {
if len(s.items) == 0 {
return
}
s.items = s.items[:len(s.items)-1]
}
// iteratorContext 表示单个节点的遍历上下文。
// nextChildFn: 一个函数,用于获取“下一个子节点索引”。
// children: 保存该节点所有孩子的列表。
type iteratorContext struct {
nextChildFn traverseFunc
children []*nodeRef
}
// newIteratorContext 根据节点创建一个 iteratorContext,设置好遍历函数(正序/逆序) 和孩子列表。
func newIteratorContext(nr *nodeRef, reverse bool) *iteratorContext {
return &iteratorContext{
nextChildFn: newTraverseFunc(nr, reverse),
children: toNode(nr).allChildren(),
}
}
// next 从 iteratorContext 中获取下一个子节点。
// nextChildFn() 返回 (idx, hasMore),idx 为下一个子节点索引,hasMore 表示是否还有更多索引可遍历。
// 如果对应子节点非空,则返回它,否则继续取下一个,直到没有更多。
func (ic *iteratorContext) next() (*nodeRef, bool) {
for {
idx, ok := ic.nextChildFn()
if !ok {
break
}
if child := ic.children[idx]; child != nil {
return child, true
}
}
return nil, false
}
//------------------------------------------------------------------------------
// iterator 结构体
//------------------------------------------------------------------------------
// iterator 负责按照深度优先的方式去遍历树(或者说所有节点),
// 它内部维护一个 state 栈来记录 "当前要遍历的节点列表"。
type iterator struct {
version int // 迭代器生成时记录的树版本号,用来检测并发修改
tree *tree // 所属的那颗树
state *state // 当前迭代状态
nextNode *nodeRef // 缓存下一个要返回的节点
reverse bool // 是否逆序遍历
}
// 确保 iterator 实现了 Iterator 接口
var _ Iterator = (*iterator)(nil)
// newTreeIterator 用于从 tree 和遍历选项(opts) 创建一个迭代器。
// 如果 opts&TraverseAll==TraverseAll,就返回直接的 iterator;
// 否则包装成 bufferedIterator 做“叶子/节点”过滤。
func newTreeIterator(tr *tree, opts traverseOpts) Iterator {
// 构造一个初始的 state,并把 root 节点压栈
state := &state{}
state.push(newIteratorContext(tr.root, opts.hasReverse()))
it := &iterator{
version: tr.version, // 记录当前树版本
tree: tr,
nextNode: tr.root, // 初始的 nextNode 先设成 root
state: state,
reverse: opts.hasReverse(),
}
// 如果遍历选项是 "TraverseAll" 就返回它自身
if opts&TraverseAll == TraverseAll {
return it
}
// 否则再包装一层 bufferedIterator,做“只要 leaf”/“只要 node”过滤
bit := &bufferedIterator{
opts: opts,
it: it,
}
// 预先获取下一个合法节点
bit.peek()
return bit
}
// HasNext 判断是否还有下一个节点可返回
func (it *iterator) HasNext() bool {
return it.nextNode != nil
}
// Next 返回下一个节点,或者抛错
func (it *iterator) Next() (Node, error) {
if !it.HasNext() {
return nil, ErrNoMoreNodes
}
// 检测并发修改
if it.hasConcurrentModification() {
return nil, ErrConcurrentModification
}
current := it.nextNode
it.next() // 移动到下一个
return current, nil
}
// next 进行一次 “深度优先” 式的遍历推进。
func (it *iterator) next() {
for {
ctx, ok := it.state.current()
if !ok {
// 栈空了,没有更多节点
it.nextNode = nil
return
}
// 获取当前上下文 ctx 中的下一个子节点
nextNode, hasMore := ctx.next()
if hasMore {
// 找到一个子节点,就设置为 nextNode 并压栈 => 下次再进一步
it.nextNode = nextNode
it.state.push(newIteratorContext(nextNode, it.reverse))
return
}
// 当前上下文耗尽了,把这一层弹栈,回到上一层
it.state.discard()
}
}
// 检查树版本是否发生变更
func (it *iterator) hasConcurrentModification() bool {
return it.version != it.tree.version
}
//------------------------------------------------------------------------------
// bufferedIterator 进一步对节点进行过滤
//------------------------------------------------------------------------------
// bufferedIterator 实际上对 iterator 做了一层包装:
// - 如果只需要遍历叶子 (TraverseLeaf) 或只需要遍历内部节点 (TraverseNode),
// 就在 next() 里过滤掉不符合条件的节点。
// - 同时也要处理 ErrNoMoreNodes / ErrConcurrentModification。
type bufferedIterator struct {
opts traverseOpts
it Iterator // 被包装的迭代器
nextNode Node // 缓存下一个符合过滤条件的节点
nextErr error // 缓存错误
}
// 确保 bufferedIterator 也实现了 Iterator 接口
var _ Iterator = (*bufferedIterator)(nil)
func (bit *bufferedIterator) HasNext() bool {
return bit.nextNode != nil
}
func (bit *bufferedIterator) Next() (Node, error) {
current := bit.nextNode
if !bit.HasNext() {
return nil, bit.nextErr
}
// 先把当前要返回的节点记录,然后再找下一个符合条件的
bit.peek()
// 如果下一个出现并发修改,就立即返回错误
if errors.Is(bit.nextErr, ErrConcurrentModification) {
return nil, bit.nextErr
}
return current, nil
}
// peek 负责不断从底层 it.Next() 中拿到节点,如果符合过滤条件就存下并返回。
// 如果不符合,则继续拿下一个,直到拿到符合的或没有更多。
func (bit *bufferedIterator) peek() {
for {
bit.nextNode, bit.nextErr = bit.it.Next()
if bit.nextErr != nil {
// 出错(或迭代到底)就终止
return
}
if bit.matchesFilter() {
return
}
}
}
// matchesFilter 根据 bit.opts 里的 TraverseLeaf / TraverseNode 判断节点是否需要
func (bit *bufferedIterator) matchesFilter() bool {
// 如果需要遍历叶子节点,且当前 nextNode 是 Leaf
if (bit.opts & TraverseLeaf) == TraverseLeaf && bit.nextNode.Kind() == Leaf {
return true
}
// 如果需要遍历内部节点,且当前 nextNode 不是 Leaf
if (bit.opts & TraverseNode) == TraverseNode && bit.nextNode.Kind() != Leaf {
return true
}
return false
}
下面分几个角度做更详细的说明:
iterator
使用了一个state
对象,其本质是个栈。state
每个元素都是*iteratorContext
,里面保存了“当前节点的孩子列表”和一个 “取下一个孩子索引的函数nextChildFn
”。- 当遍历一个节点时,我们就把它的孩子“上下文”压栈,下一次调用
Next()
时就会从栈顶元素开始继续取下一个孩子。 - 当这一层所有孩子都遍历完,
iteratorContext
会被discard()
出栈,然后回到上一层。
好处:这种实现结构相当于一个手动维护的深度优先搜索(DFS)。但不是用简单的递归,而是用迭代 + 栈的方式来控制遍历顺序,方便在 Next()
的调用间歇中保持遍历状态。
type iteratorContext struct {
nextChildFn traverseFunc
children []*nodeRef
}
children
: 保存一个节点的所有子节点指针[]*nodeRef
。nextChildFn
: 是一个函数类型type traverseFunc func() (int, bool)
,每次调用返回下一个要访问的下标 idx,以及是否还有更多子节点bool
。- 根据
TraverseReverse
设置不同的traverseFunc
,可以让遍历顺序是正序或倒序。
- 根据
在 newIteratorContext(nr *nodeRef, reverse bool)
里会调用 newTraverseFunc(nr, reverse)
做不同的遍历策略,比如:
- 正序:子节点从左到右索引依次返回
(0,1,2,...)
。 - 逆序:子节点从右到左索引依次返回
(N-1, N-2, ...)
。
这样就统一了获取子节点下标的逻辑,写在一个函数里,避免写两套循环。
-
初始
newTreeIterator(...)
会创建一个iterator
,并把root
节点包装成一个iteratorContext
压入state.items
栈顶。iterator.nextNode
初始设为root
。
-
HasNext()
- 判断
iterator.nextNode != nil
,如果不为 nil 就说明还有节点可返回。 - 如果已经遍历完,会把
nextNode
设成 nil,则HasNext() == false
。
- 判断
-
Next()
- 检查
HasNext()
,若没有则抛ErrNoMoreNodes
。 - 检查
hasConcurrentModification()
,若版本不一致,则抛ErrConcurrentModification
。 - 记下当前的
it.nextNode
作为要返回的 Node,然后调用it.next()
来推进到下一个。 - 返回记录下的 Node。
- 检查
-
it.next()
- 循环:
ctx, ok := it.state.current()
取栈顶的遍历上下文。如果空了说明已经完全遍历结束,nextNode = nil
。ctx.next()
会去调用ctx.nextChildFn()
,得到(idx, true/false)
。- 如果
true
,说明拿到一个子节点索引idx
,并且child := ctx.children[idx] != nil
。- 找到后,就
it.nextNode = child
,并把newIteratorContext(child, it.reverse)
压栈,返回。
- 找到后,就
- 如果
false
,说明当前节点全部孩子都取完了,就把这个ctx
出栈 (state.discard()
),回到上一个层级,继续循环。
- 如果
- 这个过程正是深度优先遍历的“往下走 / 回溯”逻辑。
- 循环:
有时我们只想遍历叶子节点(TraverseLeaf
),或只想遍历内部节点(TraverseNode
)。为了不破坏底层 DFS 的逻辑,作者选择再包装一层迭代器:
type bufferedIterator struct {
opts traverseOpts
it Iterator // 底层的迭代器
nextNode Node // 缓存下一个符合筛选条件的节点
nextErr error
}
HasNext()
: 判断自身的nextNode != nil
。Next()
: 返回当前nextNode
,然后再去peek()
下一个。peek()
: 连续调用it.Next()
获取下一个节点,对照opts
判断是否为叶子或内部节点,若符合就停止,不符合就继续要下一个。
这样就只在最外层做一次过滤,大大简化了逻辑。底层 DFS 不需要关心“是不是要遍历叶子/节点”;它把所有节点都遍历出来即可,最后交给 bufferedIterator
决定保留还是跳过。
func (it *iterator) hasConcurrentModification() bool {
return it.version != it.tree.version
}
- 在
Next()
时若检测到不一致,就报ErrConcurrentModification
。 - 这样可快速失败(fail-fast),防止在迭代树时有人又插入/删除导致数据结构不一致。
- 在
newTraverseFunc(n *nodeRef, reverse bool)
里,会根据reverse
来返回不同的遍历顺序函数。 - 例如正序函数会从
0 -> 1 -> 2 -> ... -> nChildren-1
,逆序函数会从nChildren-1 -> ... -> 0
。 - 在 node4 / node16 / node48 / node256 内部,也要分别考虑它们的实现方式(有时还有 “zeroChild” 特殊的索引)来正确生成遍历顺序。
iterator 在这段代码里采用了一个**“手动维护深度优先栈”**的思路,每次 Next()
就弹出一个节点并向下压栈其子节点,直到整棵树都被遍历完毕。关键要点有:
iteratorContext
:把“当前节点如何按顺序返回子节点索引”的逻辑封装成nextChildFn
+children
。- 栈
state
:深度优先遍历不再用递归,而是每次Next()
手动推进。 bufferedIterator
:外层包装做过滤,避免在核心 DFS 代码里插大量 if-else。- 版本检测:出现结构修改后,迭代器快速失败。
这使得整个迭代实现既可控又可扩展:可以轻松支持正序/逆序、叶子/内部节点过滤、前缀过滤等多种场景,且不会把核心遍历代码写得又长又复杂。通过栈和函数回调的方式,把遍历逻辑“拆”得比较干净,是值得借鉴的Go 数据结构迭代器设计思路。