在Go语言中处理标准输入时,读取指定数量的整数、浮点数或字符串是常见需求,不同的读取方式在性能和易用性上存在明显差异,需要根据实际场景选择最合适的方案。
基础方案:使用fmt包Scan系列函数
fmt包的Scan、Scanf、Scanln函数是最基础的标准输入读取方式,使用起来比较简单,适合输入量较小的场景。
如果要读取n个整数,可以直接循环调用fmt.Scan:
package main
import (
"fmt"
)
func main() {
var n int
// 先读取要输入的整数个数
fmt.Scan(&n)
nums := make([]int, n)
for i := 0; i < n; i++ {
// 循环读取n个整数
fmt.Scan(&nums[i])
}
fmt.Println(nums)
}
读取浮点数和字符串的逻辑类似,只需要把变量类型换成float64或者string即可:
package main
import (
"fmt"
)
func main() {
var n int
fmt.Scan(&n)
// 读取n个浮点数
floats := make([]float64, n)
for i := 0; i < n; i++ {
fmt.Scan(&floats[i])
}
// 读取n个字符串
strs := make([]string, n)
for i := 0; i < n; i++ {
fmt.Scan(&strs[i])
}
fmt.Println(floats, strs)
}
这种方式的缺点是每次调用fmt.Scan都会进行格式解析,输入量较大时性能较差,而且如果输入格式不符合预期,容易出现读取错误。
高效方案:使用bufio.Reader读取
bufio.Reader提供了带缓冲的读取能力,性能比fmt包的Scan系列函数更好,适合处理大量输入的场景。
我们可以通过bufio.NewReader创建读取器,然后使用ReadString或者ReadBytes读取整行内容,再自行解析数据:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取第一行,获取要输入的整数个数
line, _ := reader.ReadString('n')
line = strings.TrimSpace(line)
n, _ := strconv.Atoi(line)
nums := make([]int, n)
// 读取后续的整数行,假设所有整数在同一行,用空格分隔
dataLine, _ := reader.ReadString('n')
dataLine = strings.TrimSpace(dataLine)
strArr := strings.Split(dataLine, " ")
for i := 0; i < n; i++ {
nums[i], _ = strconv.Atoi(strArr[i])
}
fmt.Println(nums)
}
如果需要读取n个字符串,只需要调整解析逻辑即可,不需要做类型转换:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('n')
line = strings.TrimSpace(line)
n := 0
// 简单解析个数,实际场景可以用strconv.Atoi
for _, c := range line {
n = n*10 + int(c-'0')
}
strs := make([]string, n)
dataLine, _ := reader.ReadString('n')
dataLine = strings.TrimSpace(dataLine)
strs = strings.Split(dataLine, " ")
fmt.Println(strs)
}
更简便的高效方案:使用bufio.Scanner
bufio.Scanner是Go 1.1之后引入的更简便的读取工具,默认按行扫描,也可以自定义分割函数,使用起来比bufio.Reader更方便。
读取指定数量整数的示例:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 设置扫描器按空格、换行等空白字符分割
scanner.Split(bufio.ScanWords)
// 先读取个数
scanner.Scan()
n, _ := strconv.Atoi(scanner.Text())
nums := make([]int, n)
for i := 0; i < n; i++ {
scanner.Scan()
nums[i], _ = strconv.Atoi(scanner.Text())
}
fmt.Println(nums)
}
读取浮点数和字符串同样只需要调整转换逻辑:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanWords)
scanner.Scan()
n, _ := strconv.Atoi(scanner.Text())
// 读取浮点数
floats := make([]float64, n)
for i := 0; i < n; i++ {
scanner.Scan()
floats[i], _ = strconv.ParseFloat(scanner.Text(), 64)
}
// 读取字符串
strs := make([]string, n)
for i := 0; i < n; i++ {
scanner.Scan()
strs[i] = scanner.Text()
}
fmt.Println(floats, strs)
}
不同方案对比
我们可以通过下表对比几种方案的优缺点,方便根据场景选择:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| fmt.Scan系列 | 使用简单,无需额外处理类型转换 | 性能较差,输入量大时效率低 | 输入量小、快速验证的场景 |
| bufio.Reader | 性能较好,可自定义读取逻辑 | 需要手动处理字符串分割和类型转换,代码稍复杂 | 输入量大、需要自定义解析逻辑的场景 |
| bufio.Scanner | 性能较好,使用简便,自带分割逻辑 | 默认最大token长度是64*1024,超长内容需要手动调整 | 大部分常规输入场景,尤其是算法题输入处理 |
注意事项
- 使用
fmt.Scan读取字符串时,默认遇到空白字符就会停止,无法读取包含空格的字符串,如果需要读取带空格的字符串,建议使用bufio.Reader的ReadString按行读取。 - 处理输入时一定要考虑错误情况,比如输入的内容无法转换为目标类型时,需要做错误处理,避免程序崩溃。
- 如果输入的数据量非常大,建议使用
bufio.Reader并合理设置缓冲区大小,进一步提升读取效率。
实际开发中,算法题场景优先选择bufio.Scanner,兼顾性能和易用性;业务开发中如果输入量小,用fmt.Scan更简洁,输入量大则选择bufio.Reader。