tar.writer需配对调用writeheader()和io.copy()写入内容,目录size设0且typeflag为typedir,路径须posix化并校验无“..”,大文件应流式写入,tar.gz须按tw→gw→f顺序关闭。

tar.Writer 不会自动读文件内容,只写你给的字节流。直接用 os.Open() + io.Copy() 是最稳妥的路径,但必须配对处理 Header.Size 和实际读取长度。
tar.WriteHeader() 后必须调用 io.Copy(tw, reader),不能跳过
常见错误是只调了 tw.WriteHeader() 就以为文件已写入,结果生成的 tar 包里只有 header、没有 body,解压时看到空文件或报 archive/tar: invalid tar header。
-
tw.WriteHeader()只写头部,不写内容;io.Copy(tw, src)才真正把文件体写进去 - 若用
strings.NewReader()或bytes.NewReader()模拟内容,务必设header.Size = int64(len(content)),否则 size 和实际字节数不一致会导致解析错位 - 对目录条目(
IsDir() == true),header.Size应为 0,并手动设header.Typeflag = tar.TypeDir
打包路径必须是 POSIX 风格相对路径,且不能含 ..
Windows 下 filepath.Walk 返回的是 \ 分隔路径,而 tar 规范要求 /;更危险的是,若传入 ../../etc/passwd 这类路径,解压时可能覆写宿主机任意位置。
- 统一用
filepath.ToSlash(relPath)转换分隔符 - 用
filepath.Rel(root, absPath)获取相对于归档根的路径,再检查是否含".."或是否为绝对路径:strings.Contains(rel, "..") || strings.HasPrefix(rel, "/") - 禁止直接使用
filepath.Abs()或用户输入未清洗的路径作为header.Name
大文件打包别让内存爆掉:分块 or 直接流式写入
对几百 MB 以上的文件,io.Copy(tw, f) 看似简单,但底层仍可能触发大量内存分配(尤其当 *os.File 的 read buffer 被反复填充时)。虽然 tar.Writer 本身是流式,但误用方式会让它“假装”在缓存整块数据。
- 优先用
os.Open()得到的*os.File直接传给io.Copy()—— 它支持底层 syscall read,不会额外拷贝到 Go heap - 若需控制 chunk 大小(例如限速、打日志),手动用
make([]byte, 32*1024)+f.Read()+tw.Write()分块写,避免一次性ReadAll或bytes.Buffer - 注意:不要在循环里重复
defer f.Close(),容易泄漏文件句柄
打包 tar.gz 时 gzip.Writer.Close() 顺序不能错
漏掉 gw.Close() 或顺序颠倒,生成的 .tar.gz 文件解压会报 gzip: invalid header 或只解出前几个文件——因为 gzip 尾部校验和、ISIZE 字段只在 Close() 时写入。
- 正确顺序:
tw.Close()→gw.Close()→f.Close() - 不用
defer gw.Close(),因为tw.Close()若失败,gw.Close()仍会执行并掩盖真实错误 -
gw.Close()可能返回非-nil error(如io.WriteShort),必须显式检查
..、一次忘记的 gw.Close()、或者把符号链接当普通文件 Stat() 导致 Size=0,都足以让整个归档在下游静默失效。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











