
本文介绍如何避免将整个 http 响应体加载到内存,而是直接以流式方式、按需写入文件的指定 seek 位置,显著降低内存占用并提升大文件处理效率。
本文介绍如何避免将整个 http 响应体加载到内存,而是直接以流式方式、按需写入文件的指定 seek 位置,显著降低内存占用并提升大文件处理效率。
在 Go 中处理多个 HTTP 响应并将其并行写入同一文件的不同偏移位置(例如实现分片下载、多源合并写入等场景)时,常见误区是使用 ioutil.ReadAll 或 bytes.Buffer 全量读取响应体——这会导致内存随响应体积线性增长,甚至触发 OOM。
正确做法是跳过中间内存缓冲,直接将 http.Response.Body 流式复制到已定位的文件句柄中。核心工具是 io.Copy,它以固定大小的缓冲区(默认 32KB)逐块读取源 io.Reader 并写入目标 io.Writer,全程零内存堆积:
client := &http.Client{}
req, err := http.NewRequest("GET", os.Args[1], nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 注意:关闭 Body,而非 resp
// 获取目标写入偏移(例如从元数据或计算得出)
offset := getSeekOffset() // 替换为你的逻辑
_, err = fs.Seek(int64(offset), io.SeekStart)
if err != nil {
log.Fatal("seek failed:", err)
}
// 流式写入:边读边写,内存恒定
written, err := io.Copy(fs, resp.Body)
if err != nil {
log.Fatal("copy failed:", err)
}
log.Printf("wrote %d bytes to offset %d", written, offset)✅ 关键优势:
- 内存恒定:无论响应体是 1MB 还是 1GB,内存占用仅由内部缓冲区决定(约 32KB);
- 无额外拷贝:数据不经过用户态缓冲区,直接从网络 socket → 内核页缓存 → 磁盘;
- 错误可追溯:io.Copy 返回实际写入字节数与首个错误,便于监控和重试。
⚠️ 注意事项:
- 务必调用 resp.Body.Close()(非 resp.Close()),否则连接无法复用且可能泄漏;
- fs 必须是支持 io.Writer 的可寻址文件(如 *os.File),且已以 os.O_CREATE | os.O_WRONLY 模式打开;
- 若需并发写入多个偏移,请确保文件句柄 fs 是线程安全的(*os.File 本身是并发安全的),但需自行协调 seek + write 的原子性(例如用 syscall.Pwrite 或加锁);
- 对于超长响应,建议配合 context.WithTimeout 控制请求生命周期,防止无限阻塞。
总结:io.Copy(dst, src) 是 Go 中流式 I/O 的基石函数。在 HTTP 下载到文件场景中,它替代了所有“先读全再写”的低效模式,是内存友好、高性能、符合 Go 语言惯用法的标准解法。

















