
在 go 编程竞赛中,`fmt.scanf` 读取大量整数时性能极差(10⁵ 个整数耗时超 2.5 秒),根本原因在于其格式解析开销大、无缓冲且频繁系统调用;推荐使用 `bufio.scanner` 或 `bufio.reader` 配合 `strconv` 实现零分配、高吞吐的数字解析。
Go 的 fmt.Scanf 虽语法简洁,但在高频、大批量数字读取场景(如 ACM/ICPC 类编程竞赛)中表现严重不佳。其低效根源在于:
- 每次调用均需执行完整的格式字符串解析(如 "%d");
- 底层依赖 os.Stdin.Read() 的小块读取,触发大量系统调用;
- 内部使用反射和通用类型转换逻辑,无法内联优化;
- 不支持预分配缓冲区,易引发内存分配与 GC 压力。
✅ 推荐方案:bufio.Scanner + strconv(兼顾简洁与性能)
这是竞赛中最常用、平衡性最佳的方案——启用缓冲、避免格式解析、复用内存:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024) // 关键:增大缓冲区,避免扫描器内部切片扩容
// 读取单个整数
if scanner.Scan() {
s := strings.TrimSpace(scanner.Text())
if n, err := strconv.ParseInt(s, 10, 64); err == nil {
fmt.Printf("int64: %d\n", n)
}
}
// 读取多行/空格分隔的整数(例如一行含 10⁵ 个数字)
if scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line) // 按空白分割(兼容空格、制表符、换行)
for _, field := range fields {
if n, err := strconv.ParseInt(strings.TrimSpace(field), 10, 64); err == nil {
// 处理 n...
}
}
}
}⚠️ 进阶优化:bufio.Reader + 手动字节解析(极致性能)
当输入纯数字流(如每行一个整数,无多余空格),可绕过字符串转换,直接逐字节解析,减少内存分配:
func readInt(r *bufio.Reader) (int64, error) {
var n int64
var b byte
var neg bool
var err error
// 跳过前导空白
for {
if b, err = r.ReadByte(); err != nil {
return 0, err
}
if b != ' ' && b != '\t' && b != '\n' && b != '\r' {
break
}
}
// 处理符号
if b == '-' {
neg = true
b, err = r.ReadByte()
if err != nil {
return 0, err
}
} else if b == '+' {
b, err = r.ReadByte()
if err != nil {
return 0, err
}
}
// 解析数字
for ; b >= '0' && b <= '9'; b, err = r.ReadByte() {
n = n*10 + int64(b-'0')
if err != nil && err != io.EOF {
return 0, err
}
}
if neg {
n = -n
}
return n, nil
}
// 使用示例
reader := bufio.NewReader(os.Stdin)
for i := 0; i < 100000; i++ {
n, err := readInt(reader)
if err != nil {
break
}
// 处理 n...
}? 关键注意事项:
- ✅ 始终调用 scanner.Buffer() 设置足够大的缓冲区(如 1MB),否则默认 64KB 缓冲在大输入下会频繁重分配;
- ✅ strconv.Parse* 函数比 fmt.Sscanf 快 5–10 倍,且不依赖格式字符串;
- ❌ 避免 ioutil.ReadAll(os.Stdin)(已弃用,应改用 io.ReadAll),它将整个输入加载到内存,对超大输入不安全,且末尾换行处理易出错;
- ✅ 对 uint64 / float64,分别使用 strconv.ParseUint / strconv.ParseFloat,注意指定 bitSize(如 64);
- ? 若需交互式输入(非批量),仍可用 bufio.NewReader(os.Stdin) + ReadString('\n'),但务必配合 strings.TrimSpace 清理换行符。
? 总结:在 Go 算法竞赛中,抛弃 fmt.Scanf 是性能优化的第一步。优先采用 bufio.Scanner(简单场景)或 bufio.Reader(极限性能需求),搭配 strconv 系列函数完成类型转换,可将 10⁵ 整数读取时间从 2.5 秒降至 ~30–80ms(实测提升 30×+),真正达到生产级 I/O 效率。










