Go语言输入详解:fmt.Scan 和 bufio.NewReader 到底该怎么选?
前言
刚开始学习 Go 的时候,输入功能看起来非常简单:
var age int
fmt.Scan(&age)
输入一个数字,程序就能正常运行。
但随着学习深入,很多问题会逐渐出现:
- 为什么输入一句话只能读到第一个单词?
- 为什么带空格的内容总是读取不完整?
- 为什么有的教程使用 fmt.Scan,有的却使用 bufio.NewReader?
- Scan、Scanln、Scanf 到底有什么区别?
很多初学者并不是不会用,而是不知道应该在什么场景下选择什么工具。
本文就从实际开发角度出发,彻底搞懂 Go 语言最常用的输入方式。
为什么会有两套输入方案
很多人第一次接触 Go 时都会疑惑:
都是读取用户输入,为什么标准库要提供两种写法?
例如:
fmt.Scan(&name)
和
reader := bufio.NewReader(os.Stdin)
看起来都能读取数据。
实际上:
它们解决的是两种不同的问题。
fmt.Scan 适合读取“单词”
例如:
var name string
fmt.Scan(&name)
fmt.Println(name)
输入:
张三
输出:
张三
完全正常。
但如果输入:
张三 李四 王五
结果:
张三
只有第一个单词被读取。
因为:
fmt.Scan 默认以空格、Tab、换行作为分隔符。
它真正读取的是:
一个字段(token)
而不是:
一整行文本
bufio.NewReader 适合读取整行
如果需求变成:
请输入你的个人介绍:
用户可能输入:
大家好,我来自北京,今年25岁。
这时候:
var intro string
fmt.Scan(&intro)
得到的结果只有:
大家好,我来自北京,今年25岁。
前面没有空格还能正常工作。
但如果输入:
大家好 我来自北京 今年25岁
结果变成:
大家好
后面的内容全部丢失。
因此需要:
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\\n')
一次读取整行。
fmt.Scan 和 bufio.NewReader 的本质区别
| 读取方式 | 按字段读取 | 按行读取 |
| 空格处理 | 遇空格停止 | 保留空格 |
| 换行处理 | 遇换行停止 | 可读取整行 |
| 使用难度 | 简单 | 稍复杂 |
| 适用场景 | 数字、单词 | 长文本、句子 |
记住一句话:
fmt.Scan 读字段,bufio.NewReader 读整行。
bufio.NewReader 括号里到底填什么
这是很多初学者都会问的问题。
例如:
reader := bufio.NewReader(???)
这里到底写什么?
答案是:
输入源
只要实现了:
io.Reader
接口。
都可以传进去。
从键盘读取
最常见:
reader := bufio.NewReader(os.Stdin)
这里:
os.Stdin
表示标准输入。
也就是键盘。
从文件读取
file, _ := os.Open("data.txt")
reader := bufio.NewReader(file)
此时数据来自文件。
从字符串读取
reader := bufio.NewReader(
strings.NewReader("hello world"),
)
常用于测试。
为什么很多项目喜欢用 bufio
很多新手会问:
既然 Scan 更简单,为什么很多项目代码不用它?
原因主要有两个。
第一:更灵活
例如:
请输入一句话:
使用 Scan:
fmt.Scan(&msg)
遇到空格就结束。
而 Reader:
line, _ := reader.ReadString('\\n')
直接获得完整内容。
第二:更适合处理大量输入
例如:
在线判题
日志分析
文件处理
这些场景可能需要读取大量数据。
bufio 内部维护缓冲区。
能够减少系统调用次数。
性能通常更好。
Scan 的三个兄弟函数
很多人只知道:
fmt.Scan()
其实还有两个常用兄弟。
Scan
按空格或换行分隔。
var name string
var age int
fmt.Scan(&name, &age)
输入:
张三 25
输出:
name = 张三
age = 25
Scanln
读取到换行为止。
fmt.Scanln(&name, &age)
输入:
张三 25
正常。
但输入:
张三 25 程序员
可能导致读取异常。
因此:
Scanln 更适合固定数量输入。
Scanf
按照指定格式读取。
fmt.Scanf(
"姓名:%s 年龄:%d",
&name,
&age,
)
必须输入:
姓名:张三 年龄:25
格式不匹配就会失败。
两个最容易踩的坑
忘记写 &
错误写法:
var age int
fmt.Scan(age)
报错。
原因:
Scan需要变量地址
正确:
fmt.Scan(&age)
Reader 读取后忘记去掉换行符
例如:
line, _ := reader.ReadString('\\n')
此时:
line
实际内容:
hello\\n
包含换行。
因此通常需要:
line = strings.TrimSpace(line)
或者:
line = strings.TrimRight(
line,
"\\r\\n",
)
一张图看懂如何选择
需要读取数字?
│
▼
fmt.Scan
需要读取单词?
│
▼
fmt.Scan
需要读取一句话?
│
▼
bufio.NewReader
需要读取文件?
│
▼
bufio.NewReader
需要读取大量数据?
│
▼
bufio.NewReader
需要固定格式解析?
│
▼
fmt.Scanf
常见面试题
fmt.Scan 和 bufio.NewReader 的区别是什么?
回答:
fmt.Scan 按空格和换行分隔读取字段。
bufio.NewReader 可以按行读取,
能够保留空格内容。
实际开发中:
简单输入通常使用 Scan。
需要读取整行文本、
文件内容或者大量数据时,
更常使用 bufio.NewReader。
总结
很多初学者会把:
fmt.Scan
和:
bufio.NewReader
理解成两种功能重复的输入方式。
实际上并不是。
它们分别解决不同问题:
- Scan 负责读取字段
- Reader 负责读取整行
因此记住一句话即可:
读数字、读单词,用 fmt.Scan;读句子、读文件、读大量数据,用 bufio.NewReader。
理解这一点之后,以后再遇到 Go 输入相关问题,基本就知道该选择哪种方案了。

