
在 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: %dn", 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 效率。