最稳妥方式是用io.Copy流式写入文件,避免内存全量加载;需检查StatusCode、显式Close Body、正确处理路径与错误。

用 io.Copy 直接把 http.Response.Body 写进文件最稳妥
Go 里最常用也最安全的方式,就是拿 http.Get 得到响应后,用 io.Copy 把 Response.Body 流式写入打开的文件。它内部自动处理缓冲、分块读写,不会爆内存,也不需要手动循环 Read/Write。
常见错误是直接用 ioutil.ReadAll (已弃用)或 io.ReadAll 先全读进内存——大文件(比如 500MB 的 zip)会直接 OOM。
- 必须检查
os.OpenFile的 error,尤其权限不足或路径不存在时返回no such file or directory -
http.Get后务必调用resp.Body.Close(),否则连接不释放,发大量请求会卡死在too many open files - 如果目标 URL 返回非 2xx 状态码,
http.Get默认不报错,得自己判断resp.StatusCode
resp, err := http.Get("https://example.com/data.bin")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Fatalf("HTTP %d", resp.StatusCode)
}
f, err := os.OpenFile("out.bin", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
if err != nil {
log.Fatal(err)
}
需要带进度或超时控制?包一层 io.TeeReader 或用 context
原生 io.Copy 不提供进度回调,也不响应取消信号。真要加进度条或限制总耗时,得自己封装。
比如下载大文件时用户点了取消,或者等了 30 秒还没开始传数据,就得中断。
立即学习“go语言免费学习笔记(深入)”;
- 用
context.WithTimeout包住http.Get,能控制建立连接 + 首字节到达的总时间 - 想实时知道写了多少字节?用
io.TeeReader(resp.Body, progressWriter),其中progressWriter实现io.Writer接口只计数不存数据 - 别用
time.AfterFunc单独杀 goroutine,容易漏关 Body 和文件句柄
写入前要校验文件完整性?先算哈希再写,别边写边算
有些场景要求写入后的文件和源一致(比如固件升级),但又不想下完再读一遍算校验和——太慢。正确做法是:用 hash.Hash(如 sha256.New())做 io.MultiWriter 的一个目标,和文件一起写。
- 顺序很重要:必须
io.Copy到io.MultiWriter(file, hasher),不能反过来 - 写完后立刻调用
hasher.Sum(nil)拿结果,别等文件 close 之后再算 - 注意
Content-MD5或ETag响应头可能不是标准 MD5,有些服务返回的是 base64 编码的二进制 MD5,得解码比对
Windows 下路径含中文或空格导致 open out.txt: no such file or directory
这不是 Go 的 bug,是系统调用层面的问题。Go 的 os.OpenFile 在 Windows 上对路径编码很敏感,尤其当工作目录是中文路径、或文件名含空格/括号时,容易因 shell 层面转义失败而报错。
- 绝对路径优先:用
filepath.Abs("out.txt")转成完整路径再传给OpenFile - 避免拼接路径用
+,一律用filepath.Join("dir", "file.txt") - 如果从命令行参数读路径,记得用
flag.String而不是os.Args手动解析,后者在 PowerShell 里空格会被截断
网络流写文件这事,核心就一条:别碰内存全量加载。剩下的问题,基本都出在路径、超时、状态码、关闭时机这四个点上,盯住它们就行。


















