bufio.Scanner是Go读取行最稳的选择,因其专为按行读设计,自动兼容\n和\r\n,内置缓冲与边界检查,不因超长行panic(默认64KB,可调),scanner.Text()直接返回干净字符串。

bufio.Scanner 是 Go 读取行最稳的选择,别碰 ReadString('\n') ——它在 Windows 下会卡住,且返回的字符串带 \r,处理不干净就出 bug。
为什么 Scanner 是默认推荐
它专为按行读设计,自动兼容 \n 和 \r\n,内部有缓冲和边界检查,不会因超长行 panic(默认上限 64KB,可调)。你不用管换行符怎么来的,scanner.Text() 拿到的就是干净字符串。
常见翻车点:bufio.NewReader(os.Stdin).ReadString('\n') 在 Windows 输入后没反应——因为输入的是 \r\n,ReadString 卡在等 \n,而 \r 还卡在缓冲区里,下一次读直接拿到空行。
- 初始化:
scanner := bufio.NewScanner(os.Stdin) - 循环读:
for scanner.Scan() { line := scanner.Text() } - 判断结束条件直接用
line == "quit",不用strings.TrimSpace救火 - 读完必须检查
scanner.Err():只有io.EOF可忽略,其他错误(比如io.ErrUnexpectedEOF)得报出来 - 别在
scanner.Scan() == false后还调scanner.Text()——它返回的是上一次成功的内容,不是当前状态
Scanner.Buffer 怎么调才不爆错
默认单行最多 64KB,超长就报 scanner.ErrTooLong。这不是 panic,但会中断循环,且 scanner.Err() 返回这个错误。
真要读超长行(比如用户粘贴大段 JSON),得在 Scan() 前调:
scanner.Buffer(make([]byte, 0, 1<p>注意:第一个参数是底层数组,第二个才是最大 token 长度;两个值都得设够,只调大第二个没用。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0"><img src="https://img.php.cn/upload/manual/001/589/237/6a6adeed24a4a355.png" alt="Go语言(Golang)1.26.0" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="overflowclass">Go语言(Golang)1.26.0</a> <p class="overflowclass">Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。</p> </div> <a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 如果文件是 CSV 或日志,平均行长超 8KB,建议初始容量也设大点(比如
make([]byte, 0, 16*1024)),避免频繁 realloc - 配合
scanner.Bytes()用能省掉字符串转换开销,适合只做字节匹配的场景(比如找某个二进制前缀) - 别在循环里反复调
Buffer——只调一次,生效到整个扫描周期
什么情况下非用 bufio.Reader 不可
需要跳过空白、读单个字符、混合解析(比如先读一个数字再读一整行)、或必须保留换行符本身时,Reader 才是正解。
但它不能和 Scanner 共用同一个 os.Stdin——哪怕只建过一个 Scanner,再建 Reader 就会读到空或错位数据。
-
reader.ReadString('\n')返回的字符串带\n,Windows 下还可能带\r,务必用strings.TrimRight(line, "\r\n")清理 - 读密码这类敏感输入,用
reader.ReadString('\n')+TrimRight比fmt.Scanln更可控 - 处理中文或 emoji,用
reader.ReadRune(),别用ReadByte()——后者只认 ASCII 字节 - 需要 peek 或 unread,
Reader提供Peek()和UnreadRune(),Scanner没这能力
混用 fmt.Scan、Scanner、Reader 为什么必丢数据
fmt.Scan 内部偷偷用了缓冲,但不暴露出来。比如 fmt.Scan(&n) 读完数字 123 后,回车留下的 \n 还在 stdin 缓冲区里。紧接着 scanner.Scan() 会立刻返回 true,scanner.Text() 是空字符串——你以为程序卡住了,其实它已经“读完了”。
- 现象:输完数字按回车,光标停住不动,等你再输一行,结果第一行处理成了空
- 根本原因:不同读取器各自维护缓冲,底层
os.Stdin的读位置被多个对象错乱推进 - 解法:全程只用一种读取方式;如需混合解析,全用
Reader+ReadString/ReadInt手动拆 - 临时救急:用
reader.Discard(1)清掉残留\n,但不如从源头统一读取逻辑
真正难处理的不是“怎么读”,而是“谁在读、什么时候读、缓冲区里还剩啥”。只要同一输入源只交给一个读取器,90% 的卡死、丢行、多 \r 问题就消失了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










