Scanner 读到“空行”就停是因为行长度超限而非真遇到空行;默认缓冲区64KB,超长行触发bufio.ErrTooLong导致Scan()返回false;应检查scanner.Err()并用scanner.Buffer()扩大缓冲区。

为什么 Scanner 读到空行就停了?
默认情况下 bufio.Scanner 的缓冲区上限是 64KB,遇到超长行(比如日志中混入的 base64 字段、单行 JSON)会直接返回 scanner.Err() == bufio.ErrTooLong,且后续调用 scanner.Scan() 返回 false,看起来像“卡在空行”或“提前结束”。这不是空行问题,而是行长度越界导致扫描器终止。
- 用
scanner.Err()检查是否为bufio.ErrTooLong,而不是只看Scan()返回值 - 可通过
scanner.Buffer(make([]byte, 64*1024), 1 手动扩大缓冲区上限(第二个参数是最大令牌长度,设为 1MB) - 若文件含真正不可控的超长行,建议改用
bufio.Reader.ReadLine()或逐字节读取
Scanner 默认按什么分隔符切行?
bufio.Scanner 默认使用 bufio.ScanLines 作为分隔函数,它识别 \n、\r\n、\r 三种换行符,并**自动剥离**这些换行符。注意:\r 单独出现时也会被当作行结束,这在处理旧 Mac 文件或某些串口输出时可能引发意外切分。
- Windows 文件(
\r\n)和 Unix 文件(\n)都能正确处理 - 若需保留换行符,不能用
scanner.Text(),应改用scanner.Bytes()并自行追加(但要注意Bytes()返回的是内部缓冲区切片,下一次Scan()后失效) - 自定义分隔符可用
scanner.Split(),例如按空行分割:传入bufio.ScanLines改写逻辑,或直接用bytes.Split预处理
如何安全地边读边解析 CSV 或 JSON 行?
每行一个 JSON 对象(JSON Lines)或 CSV 记录时,不能假设 scanner.Text() 返回的字符串一定合法——网络传输截断、编码错误、BOM 头都可能导致解析失败。必须对每一行单独做错误隔离。
- 不要把所有行存进 slice 再批量解析;每调用一次
scanner.Scan()就立即处理一行 - 对
json.Unmarshal([]byte(line), &v)做if err != nil判断,出错时打印scanner.Bytes()的原始字节(可 hex dump),方便定位乱码或截断位置 - CSV 场景下,优先用
csv.NewReader(scanner)替代手动strings.Split(),它能正确处理带引号、换行、逗号的字段
file, _ := os.Open("data.jsonl")
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Bytes() // 避免 string 转换丢失二进制数据
var record map[string]interface{}
if err := json.Unmarshal(line, &record); err != nil {
log.Printf("parse error at offset %d: %v, raw: %x", scanner.Bytes(), err)
continue
}
// 处理 record
}
Scanner 和 ReadLine 哪个更适合大文件?
性能差异不大,但语义和错误处理完全不同:Scanner 是“按逻辑行”抽象,ReadLine() 是“按物理字节边界”读取。前者更易用,后者更可控。
立即学习“go语言免费学习笔记(深入)”;
-
Scanner自动跳过空白行(如果没禁用),而ReadLine()会把空行([]byte{})也返回 -
ReadLine()遇到不完整行(末尾无换行符)时返回isPrefix=true,需循环读取拼接;Scanner在ErrTooLong时也要求你处理前缀,但逻辑更隐蔽 - 若需精确控制内存(如流式解密后按行处理),
ReadLine()更合适,因为你能复用同一块[]byte缓冲区
scanner.Bytes() 不是新分配的内存,别把它塞进 goroutine 里异步用。










