text/scanner 是 go 词法扫描器,非分词器;它按 go 语法识别 token(如 ident、int),不按空格或自定义符号切分;需按分隔符切流应使用 bufio.scanner 配 splitfunc。

text/scanner 不是分词器,它只做词法扫描;想按空格/标点切字符串,该用 bufio.Scanner 配 SplitFunc,而不是 text/scanner。
text/scanner 的 Scan() 返回的是 token 类型,不是“分词结果”
很多人误以为 text/scanner 是用来把一段文本“切成单词”的工具。它实际干的是:逐字符读入、识别 Go 风格的词法规则(比如 IDENT、INT、STRING),返回 token 类型,并提供 TokenText() 拿原始字面量。它不按空格、逗号或自定义符号切;它按语言语法切。
- 输入
"a, b + 1",Scan()会依次返回scanner.IDENT("a")、scanner.COMMA(",")、scanner.IDENT("b")、scanner.ADD("+")、scanner.INT("1") - 不会合并或跳过空白 —— 但默认
Mode会跳过它们,所以你根本看不到scanner.SPACE - 如果你需要“把字符串按
|||拆成几段”,text/scanner做不了,得换bufio.Scanner
真正按分隔符切流,用 bufio.Scanner.Split()
要实现类似 Python 的 str.split("|||") 流式效果,必须用 bufio.Scanner 并调 Split() 方法传自定义 SplitFunc。这个函数决定每次 Scan() 返回哪一段字节。
-
SplitFunc必须处理两种边界情况:atEOF == true(最后一段没分隔符)和len(data) (数据还没读全,不能贸然切) - 错误写法:直接
bytes.Index(data, []byte("|||"))然后panic或忽略atEOF→ 最后一段永远扫不到 - 正确逻辑三步:找分隔符位置 → 切出 token → 更新
data剩余部分(返回新data和advance偏移) - 示例分隔符
"|||"的最小可用实现:func SplitOnTripleBar(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } if i := bytes.Index(data, []byte("|||")); i >= 0 { return i + 3, data[0:i], nil } if atEOF { return len(data), data, nil } return 0, nil, nil }然后scanner.Split(SplitOnTripleBar)
text/scanner.TokenText() 必须紧跟 Scan() 调用
这是最常踩的坑:调了 s.Scan(),拿到 token.IDENT,却立刻去打印 s.TokenText() 发现是空。原因只有一个:TokenText() 不是缓存值,它从当前 scanner 内部指针位置往前回溯取原始字节 —— 如果中间又调了一次 Scan(),上一个 token 的文本就丢了。
- 必须写成:
if tok := s.Scan(); tok != scanner.EOF { text := s.TokenText(); ... } - 不能先批量
Scan()存 token 类型到 slice,再统一取TokenText() - 输入源要是有效的
io.Reader(如strings.NewReader("x:=1")),传nil或未初始化的 reader 会静默失败 - 如果 TokenText 总为空,优先检查是否漏了
Init()或输入源内容为空
别指望 text/scanner 支持自定义关键字或分隔逻辑
text/scanner 是 lexer,不是 parser,也不可扩展。它的 token 类型(scanner.IDENT、scanner.COMMENT 等)是硬编码在标准库里的,没有注册机制,也没有回调钩子。
- 想把
"def"当作关键字?只能在Scan()后手动比对TokenText()结果,再映射成你自己的枚举 - 想识别
"#"开头的行注释?text/scanner默认只认//和/* */,不支持改规则 - 想跳过某种空白(比如只跳空格不跳 tab)?做不到。它的
Mode只控制是否跳过空白/注释整体,不提供细粒度策略 - 遇到
token.ILLEGAL却不知道错在哪?必须设s.Error = func(s *scanner.Scanner, msg string) {...},否则错误被吞掉,连位置都不报
真正麻烦的地方在于:text/scanner 和 bufio.Scanner 名字太像,但职责完全不同。前者吃 Go 语法,后者吃任意字节流;混用时连错误提示都不会帮你指出来 —— 它们只是安静地返回不符合预期的结果。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











