Go 压缩和归档入门:gzip、tar 和 zip 的基本用法

本文讲解 Go 标准库中 compress/gzip、archive/tar 和 archive/zip 的基本使用方式,帮助初学者处理日志压缩和文件打包。

压缩和归档是很多后台工具的基础能力

服务端程序经常需要处理文件:日志按天压缩,报表打包下载,备份目录归档,上传的 zip 解开检查。Go 标准库提供了 compress/gziparchive/tararchive/zip 等包,足够覆盖很多入门和中小项目需求。

先分清两个概念:压缩是让数据变小,比如 gzip;归档是把多个文件组织成一个文件,比如 tar。.tar.gz 通常表示先 tar 归档,再 gzip 压缩。zip 则同时包含归档和压缩能力。

这篇文章用日志压缩和目录打包做例子,讲清楚基本数据流。重点仍然是 io.Readerio.Writer 的组合。

gzip 压缩字符串或文件

压缩到 writer:

func GzipData(w io.Writer, data []byte) error {
	gz := gzip.NewWriter(w)
	defer gz.Close()

	if _, err := gz.Write(data); err != nil {
		return fmt.Errorf("write gzip: %w", err)
	}
	return nil
}

使用:

var buf bytes.Buffer
if err := GzipData(&buf, []byte("hello hello hello")); err != nil {
	return err
}
fmt.Println(buf.Len())

压缩文件:

func GzipFile(dstPath, srcPath string) error {
	src, err := os.Open(srcPath)
	if err != nil {
		return fmt.Errorf("open source: %w", err)
	}
	defer src.Close()

	dst, err := os.Create(dstPath)
	if err != nil {
		return fmt.Errorf("create gzip file: %w", err)
	}
	defer dst.Close()

	gz := gzip.NewWriter(dst)
	defer gz.Close()

	if _, err := io.Copy(gz, src); err != nil {
		return fmt.Errorf("copy gzip data: %w", err)
	}
	return nil
}

gz.Close() 很重要,它会写入压缩尾部信息。忘记关闭可能得到损坏文件。

gzip 解压

func Gunzip(r io.Reader) ([]byte, error) {
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, fmt.Errorf("create gzip reader: %w", err)
	}
	defer gz.Close()

	data, err := io.ReadAll(gz)
	if err != nil {
		return nil, fmt.Errorf("read gzip: %w", err)
	}
	return data, nil
}

如果输入来自不可信来源,解压时要考虑大小限制。压缩文件可能很小,解开后很大。可以用 io.LimitReader 或限制写入目标。

tar 归档多个文件

创建 tar:

func WriteTar(w io.Writer, files map[string]string) error {
	tw := tar.NewWriter(w)
	defer tw.Close()

	for name, content := range files {
		data := []byte(content)
		header := &tar.Header{
			Name: name,
			Mode: 0644,
			Size: int64(len(data)),
		}

		if err := tw.WriteHeader(header); err != nil {
			return fmt.Errorf("write tar header: %w", err)
		}
		if _, err := tw.Write(data); err != nil {
			return fmt.Errorf("write tar body: %w", err)
		}
	}
	return nil
}

组合成 .tar.gz

func WriteTarGz(w io.Writer, files map[string]string) error {
	gz := gzip.NewWriter(w)
	defer gz.Close()

	return WriteTar(gz, files)
}

这个组合很能体现 Go 的数据流思路:tar 写到 gzip,gzip 再写到最终 writer。每层只关心自己的接口。

zip 打包

func WriteZip(w io.Writer, files map[string]string) error {
	zw := zip.NewWriter(w)
	defer zw.Close()

	for name, content := range files {
		fileWriter, err := zw.Create(name)
		if err != nil {
			return fmt.Errorf("create zip entry: %w", err)
		}
		if _, err := io.WriteString(fileWriter, content); err != nil {
			return fmt.Errorf("write zip entry: %w", err)
		}
	}
	return nil
}

写到 HTTP 响应:

w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", `attachment; filename="report.zip"`)
err := WriteZip(w, files)

zip 很适合下载多个报表文件。文件名要小心,不要直接信任用户输入的路径,避免生成奇怪目录结构。

小结

Go 标准库已经提供了常见压缩和归档能力。gzip 负责压缩单个数据流,tar 负责把多个文件归档,zip 同时处理归档和压缩。它们都围绕 io.Readerio.Writer 设计,可以自然组合。

处理压缩文件时,要记得关闭 writer,给错误加上下文,面对不可信输入时限制解压大小和文件路径。只要把数据流想清楚,Go 写这类后台工具会很顺手。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页