必须用bufio.Scanner处理用户输入和文本文件逐行读取,因其自动适配\n和\r\n、返回干净字符串、内置缓冲与边界检查;避免ReadString/ReadBytes,易卡死或返回带\r的脏数据。

直接用 bufio.Scanner,别碰 ReadString('\n') 或 ReadBytes('\n') —— 后两者在 Windows 下极易卡死、返回带 \r 的脏数据,且无法自动适配换行符差异。
什么时候必须用 bufio.Scanner?
绝大多数“用户键盘输入”或“文本文件逐行处理”场景都该无条件选它。它不是“可选项”,而是默认安全路径。
-
scanner.Scan()自动跳过缓冲区残留的换行符,不会因上一次输入留下的\n导致下一次立刻返回空行 -
scanner.Text()返回的字符串已剥离\n和\r\n,可直接做line == "quit"判断,不用strings.TrimSpace补救 - 默认单行上限 64KB,超长会报
scanner.ErrTooLong;真要读超长行,得提前调scanner.Buffer(make([]byte, 0, 1<<20), 1<<20) - 必须检查
scanner.Err():io.EOF是正常结束,其他错误(如io.ErrUnexpectedEOF)才需 panic 或 log
为什么 bufio.Reader.ReadString('\n') 容易出问题?
它只认字面 '\n',遇到 Windows 输入的 \r\n 时,\r 会被当作行内容一起返回,导致字符串末尾多一个 \r;更糟的是,\r 留在缓冲区里,后续读取可能错位。
- 每次调用后必须手动清理:
line = strings.TrimRight(line, "\r\n"),不能只 trim\r - 错误写法:
for line, err := r.ReadString('\n'); err == nil; line, err = r.ReadString('\n')—— 第一次err就可能是io.EOF,循环根本进不去 -
err == io.EOF是合法终止信号,不是错误;但io.ErrUnexpectedEOF表示数据损坏,必须处理 - 仅当你明确控制输入源(比如自己生成的 Unix 风格日志文件),或需要按非换行分隔符解析时,才考虑它
混用 fmt.Scan 和 bufio 会丢数据
fmt.Scan 内部也偷偷用了缓冲,但不暴露;它读完数字后把结尾的 \n 留在 stdin 缓冲区里,紧接着 scanner.Scan() 会立刻读到空行,现象就是“程序卡住不动”——其实它已经读完了,只是你没输新内容。
立即学习“go语言免费学习笔记(深入)”;
- 典型链:
fmt.Scan(&n)→ 输入123↵→n=123,但\n还在缓冲区 → 下次scanner.Scan()立刻返回true,scanner.Text()是空字符串 - 修复方式:整个程序统一用
scanner;若需结构化输入(如Alice 25),先用scanner.Text()读整行,再用strings.Fields()或fmt.Sscanf()解析 - 绝对不要在一个程序里同时出现
fmt.Scanln、scanner.Scan()、reader.ReadString('\n')中的任意两个
读文件和读标准输入,用法一致但细节不同
读文件时 bufio.Scanner 更宽松,但读 os.Stdin 时对缓冲竞争更敏感——哪怕只创建过一个 Scanner,再建 Reader 就会读错位。
- 读文件:优先
bufio.NewScanner(file);大文件可设更大缓冲:scanner.Buffer(make([]byte, 0, 64*1024), 64*1024) - 读
os.Stdin:只用一个Scanner实例,别和任何Reader或fmt函数混用 - 性能关键点:
scanner.Bytes()比scanner.Text()少一次内存拷贝,适合只做字节匹配(如找特定二进制模式)的场景 - 并发读取时,每个 goroutine 必须用独立的
Reader或Scanner实例,不能共享
最常被忽略的一点:所有 bufio.Scanner 使用后,必须显式检查 scanner.Err()。很多人只写 for scanner.Scan() { ... },却漏掉最后的错误判断,导致 I/O 错误静默丢失——这不是边界情况,而是真实生产环境里日志截断、输入中断的根源。


















