解析器中利用peek方法可安全预视下一个token而不消耗,实现清晰的状态隔离与错误控制;其本质是纯观察动作,支持试探性判断后再决定是否消费。
在解析器(如自定义词法分析器、表达式求值器或配置文件读取器)中,常遇到“需要向前看一个 token 或字符”的场景——比如识别 == 而非单个 =,或区分 123 和 123.45。此时若仅靠当前指针推进,容易逻辑缠绕、状态难维护。利用迭代器的 peek 方法(预视不消耗),可让代码结构清晰、状态隔离、错误可控。
peek 的本质:一次安全的“偷看”
peek 不移动游标,也不触发副作用,只返回下一个待取元素的引用或副本。它不是消费动作,而是观察动作。这正好契合解析器中“试探性判断”的需求:
- 调用
peek()得到下一个 token,做类型/值判断; - 根据判断结果,决定是否
next()消费它,或跳过、回退、组合; - 整个过程不破坏迭代器原有顺序,无需手动备份索引或重置流。
构建支持 peek 的迭代器(以 Java Iterator 为例)
标准 Iterator<t></t> 接口本身不含 peek(),需封装增强。推荐方式是包装原迭代器,缓存一个“预取项”:
- 构造时尝试调用一次
original.next(),存入nextItem字段; -
peek()直接返回该字段(若为 null 表示已到末尾); -
next()返回nextItem,并立即用原迭代器再取一个补位; -
hasNext()判断nextItem != null即可。
这样封装后,PeekableIterator<token></token> 就天然支持“看一眼再决定怎么走”,且线程不安全场景下轻量无锁。
实战:变量解析中处理 ${var} 与 ${var:default} 的歧义
在模板引擎或配置解析中,常见形如 ${name} 和 ${name:hello} 的语法。关键分歧点在 : 是否存在——而这只能靠 peek 判断 } 前是不是冒号。
- 遇到
$后确认{,进入变量名解析阶段; - 循环读取字符至
}或:,但不消费:; - 调用
peek():若返回:,则消费它,并继续解析默认值,直到下一个}; - 若
peek()返回},则直接消费并结束; - 若 peek 返回其他字符(如空格、换行),可抛出语法异常,定位精准。
这种写法把“分支预测”和“实际消费”解耦,避免了回溯缓冲区或状态机跳转,逻辑直白可测。
注意事项:别让 peek 变成陷阱
peek 虽好,但滥用会引入隐蔽问题:
- 多次调用
peek()应返回相同值(不可变语义),若底层源可变(如网络流、随机数生成器),需自行缓存; - 不要在
peek()中触发 IO、计算或修改共享状态——它应是纯观察; - 若迭代器本身已耗尽,
peek()应明确返回null或抛NoSuchElementException,而非静默失败; - 在流式解析中,peek 后必须确保有对应
next(),否则可能造成“漏读”——建议用 try-with-resources 或作用域限定其生命周期。










