os.Truncate只是“尺子”而非“剪刀”,只能设置文件总长度、丢弃末尾,无法删除中间内容;删中间需Seek定位、Write覆盖、Truncate收尾。

Go 里不能用 os.Truncate 截取中间内容,它只能从开头砍掉、或把文件缩到指定长度(丢弃末尾);要“删中间”,得靠 Seek + Write 覆盖 + Truncate 收尾。
为什么 os.File.Truncate 不是“剪刀”,而是“尺子”
Truncate 的语义非常明确:把文件逻辑长度设为指定字节数,超出部分直接丢弃,不足部分不补。它不关心当前偏移量在哪,也不支持“从 offset A 删到 B”这种区间操作。
- 调用
f.Truncate(5)后,无论文件原长是 100 还是 1000,结果都是前 5 字节保留,其余全丢 - 如果当前偏移量在位置 20,
Truncate不会把它拉回来——它只改文件大小,不动指针 - 错误用法:
f.Seek(10, io.SeekStart); f.Truncate(5)—— 这不会删掉第 10–15 字节,而是把整个文件砍成 5 字节
Seek + Write + Truncate 组合实现“精准截断”
真正需要删中间(比如回滚写了一半的 header + body),得手动移动指针、覆盖无效数据、再收缩文件。典型流程是:
- 写入前,用
f.Seek(0, io.SeekCurrent)记下起始偏移startOff - 出错后,先
f.Seek(startOff, io.SeekStart)回到起点 - 用
f.Write([]byte{0})或重复写零字节,覆盖掉已写的脏数据(可选,视格式是否要求清零) - 最后
f.Truncate(startOff)把文件长度收回到原始位置
注意:Write 覆盖时若跨块,需确保写入长度 ≥ 待清除区域长度;否则残留数据可能被后续读逻辑误解析。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
io.SectionReader 是只读“窗口”,不影响原文件偏移量
如果你只是想读某一段(比如跳过 header 读 body),io.NewSectionReader(f, offset, length) 最安全——它封装了一个新 Reader,所有读操作都在指定区间内,且完全不改变原 *os.File 的偏移量。
- 适用场景:解析多段式二进制文件、跳过日志头、提取压缩包某段
- 和
ReadAt的区别:SectionReader.Read是流式读,自动维护内部偏移;ReadAt每次都要传绝对位置 - 陷阱:
SectionReader的Size()返回的是你给的length,不是底层文件大小;越界读会返回io.EOF,不是 panic
用 Seek 获取当前偏移量的唯一可靠方式
别依赖累加 Read 返回的字节数来推算位置——缓冲区、换行符处理(如 bufio.Scanner)、底层 syscall 的分片都可能导致偏差。唯一能拿到真实偏移量的方法是:
offset, err := f.Seek(0, io.SeekCurrent)
- 这个调用不移动指针,只返回当前位置(从文件头起算的绝对偏移)
- 必须确保
f支持寻址(普通磁盘文件可以,stdin、管道、网络连接不行);检查err是否为io.ErrSeeker - 在
bufio.Reader上调用会失败——因为bufio.Reader自己维护缓冲区偏移,和底层os.File不同步;必须对原始*os.File调用
偏移量管理最易被忽略的点:混用带缓冲读写器和原始文件句柄时,Seek 的目标对象必须和你实际读写的是同一个底层 os.File 实例,否则位置永远对不上。

















