不能用 bufio.scanner 做字节级过滤,因其行导向设计破坏连续字节流,导致吞吐暴跌、跨行漏匹配、二进制 panic、位置错位;应改用 bufio.reader + 状态机实现可控字节过滤。

直接用 strings.ReplaceAll 或 ioutil.ReadFile 处理大文件会 OOM,必须基于 io.Reader 构建可组合的过滤层,边读边处理,内存恒定在 KB 级。
为什么不能用 bufio.Scanner 做字节级过滤
因为 bufio.Scanner 是行导向的,而下游(比如 pg.CopyFrom、gzip.Writer、encoding/csv.Reader)需要的是连续字节流。一旦你在 Read(p []byte) 里调用 scanner.Scan(),就会出现:
-
Read返回字节数远小于len(p),触发上游反复重试,吞吐暴跌 - 跨行敏感词(如
"违\n法")被拆开,完全漏匹配 - 二进制数据或含
\n的非文本内容直接 panic 或错位 - 内部扫描位置和外部
p缓冲区无法对齐,导致重复或丢字节
正确做法:实现 io.Reader + bufio.Reader + 状态机
用 bufio.Reader 替代 Scanner,它提供 ReadByte、Peek、Discard 等底层能力,配合显式状态维护,才能做真正可控的字节级过滤。例如跳过注释行:
- 定义状态字段:
inComment bool - 每次
Read(p []byte)中循环:br.ReadByte()逐字节判断 - 遇到
#且前一字符是\n或开头,切到inComment = true - 在
inComment状态下,只忽略直到下一个\n - 所有有效字节写入
p[n]并递增n,填满或 EOF 时返回
如何对接数据库 COPY 或 HTTP 流式响应
这类接口只认 io.Reader,不接受 string 或 []byte。你的自定义 FilteredReader 必须满足两点:
- 实现完整的
Read(p []byte) (n int, err error),不能只返回一行或一段 - 内部不能缓存整行——否则
pg.CopyFrom会卡住,HTTP 响应会延迟首字节 - 若需跳过 BOM,应在构造时用
br.Peek(3)检查并br.Discard(3) - 若要支持超长行(如 minified JSON),别依赖
Scanner,改用br.Read(p)分块读 + 手动查找\n - 务必测试
io.Copy(dst, yourReader)是否能持续输出,而不是只吐第一块
容易被忽略的缓冲区与错误传播细节
很多人写了 Read 方法,但忘了几个关键点:
-
bufio.NewReaderSize(f, 64*1024)的大小不是越大越好:超过 1MB 对性能无提升,还可能让小文件读取变慢 -
Read返回n == 0 && err == nil是合法状态(表示暂无数据,但没 EOF),调用方会重试;你不能在这里直接 return - 错误必须真实传播:比如
br.ReadByte()返回io.EOF,你要原样返回,不能吞掉或转成nil - 如果过滤逻辑中发生不可恢复错误(如解密失败),要用
fmt.Errorf("filter: %w", err)包装,保留原始堆栈 - 不要在
Read里做阻塞 I/O(如网络请求、磁盘 seek),这会让整个流卡死
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











