bufio.scanner 不适合读大文件,因其默认64kb缓冲区且整行加载易致oom;应改用 bufio.reader.readslice('\n') 手动控制读取量,平衡性能与内存。

用 bufio.Scanner 读大文件?小心内存爆掉
bufio.Scanner 默认缓冲区只有 64KB,且会把整行加载进内存。遇到超长行(比如日志里带 base64 块)或超大 CSV 行,直接触发 scanner.ErrTooLong;更隐蔽的问题是,它内部用 bytes.Buffer 动态扩容,可能把几 GB 文件的一行全吃进内存,OOM 就在下一秒。
除非你**完全确定每行不超过 1MB 且行数可控**,否则别用 Scanner 处理真正的大文件(>100MB)。替换成 bufio.Reader 手动按需读取更稳妥。
用 bufio.Reader.ReadSlice('\n') 控制单次读取量
这是平衡性能与内存的关键操作:既避免逐字节读的 syscall 开销,又防止一行撑爆内存。核心是自己管理切片生命周期,及时 copy 或处理数据,不长期持有原始 []byte。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 调用前用
r := bufio.NewReaderSize(file, 1 设为 1MB 缓冲区,减少系统调用次数 -
ReadSlice返回的是底层缓冲区内存的引用,下一次读会复用该空间——所以必须立刻处理或深拷贝,否则后续读会覆盖数据 - 遇到
io.EOF时,最后一段没换行符的数据要单独处理(ReadSlice不返回它) - 若文件含 Windows 换行
\r\n,ReadSlice停在\n,前面的\r会留在结果里,需手动bytes.TrimRight(line, "\r")
for {
line, err := r.ReadSlice('\n')
if err == io.EOF {
if len(line) > 0 {
process(bytes.TrimRight(line, "\r\n"))
}
break
}
if err != nil && err != io.ErrUnexpectedEOF {
log.Fatal(err)
}
process(bytes.TrimRight(line, "\r\n"))
}
按块读取(io.ReadFull + 偏移量)适合二进制或定长结构
如果文件是自定义二进制格式、固定长度记录(如每条 512 字节),跳过文本解析开销,直接按块读最高效。关键点在于控制每次 read 的 size,并用 file.Seek 精确跳转,避免缓冲区错位。
- 用
make([]byte, blockSize)预分配切片,避免频繁 GC -
io.ReadFull保证读满指定字节数,返回io.ErrUnexpectedEOF表示文件提前结束,此时需按实际读取长度处理末尾残块 - 不要混用
bufio.Reader和file.Read,缓冲区会导致文件偏移错乱;要么全用底层*os.File,要么全用带缓冲的 reader - 并发读多个分块?注意
file是共享的,Seek+Read不是原子操作,需加锁或每个 goroutine 持有独立*os.File(用file.SyscallConn复制 fd 更轻量)
内存映射(mmap)不是银弹,Linux/macOS 可用但要注意脏页
Go 标准库不支持 mmap,得靠 golang.org/x/sys/unix 调用 mmap。它快是因为绕过内核缓冲区,直接映射到进程虚拟内存——但代价是:整个文件(或大段)会常驻 RSS 内存,且修改后需显式 msync,否则崩溃就丢数据。
- 只读场景(如分析 TB 级日志)可大胆用:
unix.MAP_PRIVATE | unix.PROT_READ - 写入场景慎用:
unix.MAP_SHARED修改立即反映到磁盘,但若程序 panic,未msync的修改可能丢失 - Windows 上要用
syscall.CreateFileMapping,API 完全不同,跨平台成本高 - 小文件(
真正的大文件处理,往往不是“怎么读最快”,而是“怎么边读边丢、不累积中间状态”。比如用管道式处理:读一块 → 解析 → 写入数据库/发送网络 → 清空缓冲 → 继续。内存峰值取决于单次处理单元大小,而非文件总长。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










