登录 注册

 golang如何压缩文件或目录

2025-10-27  回复(0) 

在 Go 语言中,你可以使用标准库 archive/ziparchive/tar 来实现文件的压缩。

* archive/zip: 用于创建和读取 ZIP 格式的压缩文件。ZIP 是一种非常常见的压缩格式,广泛用于 Windows、macOS 和 Linux 系统。
* archive/tar: 用于创建和读取 TAR (Tape Archive) 格式的归档文件。TAR 本身并不提供压缩,但它通常与 GZIP、BZIP2 或 XZ 等压缩算法结合使用,形成 .tar.gz.tar.bz2.tar.xz 等文件。

下面我将分别介绍如何使用这两个库来压缩文件或目录。

1. 使用 archive/zip 压缩文件或目录


ZIP 格式可以压缩单个文件或多个文件到一个 ZIP 压缩包中。

示例:压缩单个文件

go
package main

import (
"archive/zip"
"io"
"os"
"path/filepath"
)

func main() {
// 要压缩的文件
sourceFile := "my_document.txt"
// 目标 ZIP 文件名
zipFileName := "my_document.zip"

// 创建一个 ZIP 文件
zipFile, err := os.Create(zipFileName)
if err != nil {
panic(err)
}
defer zipFile.Close()

// 创建一个 zip writer
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()

// 打开要压缩的文件
fileToZip, err := os.Open(sourceFile)
if err != nil {
panic(err)
}
defer fileToZip.Close()

// 在 ZIP 归档中创建一个新的文件头
// fileHeader.Name 是文件在 ZIP 归档中的路径和名称
fileHeader, err := zip.FileInfoHeader(getFileInfo(fileToZip))
if err != nil {
panic(err)
}
fileHeader.Name = filepath.Base(sourceFile) // 使用文件名作为 ZIP 中的名称
fileHeader.Method = zip.Deflate // 使用 Deflate 算法进行压缩

// 创建一个 writer 用于写入文件内容到 ZIP
writer, err := zipWriter.CreateHeader(fileHeader)
if err != nil {
panic(err)
}

// 将源文件的内容复制到 ZIP writer
_, err = io.Copy(writer, fileToZip)
if err != nil {
panic(err)
}

println("File compressed successfully to", zipFileName)
}

// getFileInfo 辅助函数,用于获取文件的 os.FileInfo
func getFileInfo(f *os.File) os.FileInfo {
info, err := f.Stat()
if err != nil {
panic(err)
}
return info
}


示例:压缩一个目录下的所有文件

要压缩一个目录,你需要遍历目录下的所有文件和子目录,并将它们添加到 ZIP 归档中。

go
package main

import (
"archive/zip"
"io"
"os"
"path/filepath"
"strings"
)

func main() {
// 要压缩的目录
sourceDir := "my_folder"
// 目标 ZIP 文件名
zipFileName := "my_folder.zip"

// 创建一个 ZIP 文件
zipFile, err := os.Create(zipFileName)
if err != nil {
panic(err)
}
defer zipFile.Close()

// 创建一个 zip writer
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()

// 遍历目录并添加文件到 ZIP
err = filepath.Walk(sourceDir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// 跳过目录本身,只处理文件
if info.IsDir() {
return nil
}

// 创建要添加到 ZIP 的文件头
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}

// 设置在 ZIP 归档中的相对路径
// 确保路径是 Unix 风格的,并且不包含前导路径分隔符
relPath, err := filepath.Rel(sourceDir, filePath)
if err != nil {
return err
}
header.Name = strings.ReplaceAll(relPath, "\\", "/") // 转换为 Unix 风格路径

// 如果是目录,则添加一个尾部斜杠
if info.IsDir() {
header.Name += "/"
}

// 设置压缩方法
header.Method = zip.Deflate

// 创建 writer 用于写入文件内容
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}

// 如果是文件,则复制内容
if !info.IsDir() {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close() // 注意:这里 defer 是在每次迭代时调用的,需要妥善处理

_, err = io.Copy(writer, file)
if err != nil {
return err
}
}

return nil
})

if err != nil {
panic(err)
}

println("Directory compressed successfully to", zipFileName)
}

// 注意:上面的 filepath.Walk 函数中,file.Close() 的 defer 可能不会在所有情况下都按预期执行,
// 因为它是在匿名函数中,并且 Walker 函数可能会在文件关闭之前返回。
// 一个更健壮的方式是直接在处理文件内容后关闭文件。

// 更健壮的 filepath.Walk 实现:
func compressDirectory(sourceDir, zipFileName string) error {
zipFile, err := os.Create(zipFileName)
if err != nil {
return err
}
defer zipFile.Close()

zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()

return filepath.Walk(sourceDir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(sourceDir, filePath)
if err != nil {
return err
}
// 转换为 Unix 风格路径
zipEntryName := strings.ReplaceAll(relPath, "\\", "/")

if info.IsDir() {
// 创建一个目录条目,并确保其名称以 "/" 结尾
if !strings.HasSuffix(zipEntryName, "/") {
zipEntryName += "/"
}
_, err := zipWriter.Create(zipEntryName)
return err
} else {
// 创建一个文件条目
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = zipEntryName
header.Method = zip.Deflate

writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}

file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close() // 这个 defer 是安全的,因为它在当前迭代中

_, err = io.Copy(writer, file)
return err
}
})
}

2. 使用 archive/tar 配合 compress/gzip 压缩目录 (例如 .tar.gz 文件)


TAR 文件本身不压缩,所以我们通常会结合 GZIP 来创建 .tar.gz 文件。

示例:压缩一个目录到一个 .tar.gz 文件

go
package main

import (
"archive/tar"
"compress/gzip"
"io"
"os"
"path/filepath"
"strings"
)

func main() {
// 要压缩的目录
sourceDir := "my_folder"
// 目标 .tar.gz 文件名
tarGzFileName := "my_folder.tar.gz"

// 创建目标文件
outFile, err := os.Create(tarGzFileName)
if err != nil {
panic(err)
}
defer outFile.Close()

// 创建 gzip writer
gzipWriter, err := gzip.NewWriterLevel(outFile, gzip.BestCompression)
if err != nil {
panic(err)
}
defer gzipWriter.Close()

// 创建 tar writer
tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()

// 遍历目录并添加文件到 tar
err = filepath.Walk(sourceDir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// 创建 tar header
header, err := tar.FileInfoHeader(info, "") // "" 表示没有链接目标
if err != nil {
return err
}

// 设置 header.Name 为文件在 tar 归档中的路径
// 确保路径是 Unix 风格的,并且不包含前导路径分隔符
relPath, err := filepath.Rel(sourceDir, filePath)
if err != nil {
return err
}
header.Name = strings.ReplaceAll(relPath, "\\", "/") // 转换为 Unix 风格路径

// 如果是目录,则添加一个尾部斜杠
if info.IsDir() {
header.Name += "/"
}

// 写入 header
if err := tarWriter.WriteHeader(header); err != nil {
return err
}

// 如果是文件,则复制文件内容
if !info.IsDir() {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close() // 注意:同样需要小心 defer 的使用

if _, err := io.Copy(tarWriter, file); err != nil {
return err
}
}

return nil
})

if err != nil {
panic(err)
}

println("Directory compressed successfully to", tarGzFileName)
}

// 更健壮的 tar.gz 压缩实现:
func compressTarGz(sourceDir, tarGzFileName string) error {
outFile, err := os.Create(tarGzFileName)
if err != nil {
return err
}
defer outFile.Close()

gzipWriter, err := gzip.NewWriterLevel(outFile, gzip.BestCompression)
if err != nil {
return err
}
defer gzipWriter.Close()

tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()

return filepath.Walk(sourceDir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(sourceDir, filePath)
if err != nil {
return err
}
// 转换为 Unix 风格路径
tarEntryName := strings.ReplaceAll(relPath, "\\", "/")

// 创建 tar header
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
header.Name = tarEntryName

// 如果是目录,则确保名称以 "/" 结尾
if info.IsDir() {
if !strings.HasSuffix(header.Name, "/") {
header.Name += "/"
}
}

// 写入 header
if err := tarWriter.WriteHeader(header); err != nil {
return err
}

// 如果是文件,则复制文件内容
if !info.IsDir() {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close() // 这个 defer 是安全的

_, err = io.Copy(tarWriter, file)
return err
}

return nil
})
}

关键点和注意事项:


1. filepath.Walk: 这是遍历目录和其子目录最常用的函数。它会为遇到的每个文件和目录调用一个回调函数。
2. zip.FileInfoHeader / tar.FileInfoHeader: 这两个函数可以将 os.FileInfo 转换为压缩包中的文件头信息。
3. zip.Writer / tar.Writer: 这些是写入压缩数据的接口。
4. io.Copy: 用于将文件的内容从源文件复制到压缩写入器。
5. 路径处理:
* 在 ZIP 和 TAR 文件中,路径通常使用正斜杠 (/) 作为分隔符,即使在 Windows 上。因此,需要使用 strings.ReplaceAll(relPath, "\\", "/") 来进行转换。
* 目录条目在 ZIP/TAR 中通常以斜杠 (/) 结尾。
* filepath.Rel 用于获取文件相对于根目录的路径,这对于在压缩包中保持目录结构非常重要。
6. 压缩方法:
* zip.Deflate 是 ZIP 中常用的压缩方法。zip 包还支持 zip.Store (不压缩)。
* 对于 .tar.gzcompress/gzip 库提供了 gzip.BestCompressiongzip.DefaultCompression 等选项来控制压缩级别。
7. 错误处理: 始终检查所有文件操作和写入操作的错误。
8. defer 的使用: 在 filepath.Walk 的回调函数中,当处理文件时,defer file.Close() 是在当前回调函数的作用域内执行的,这是安全的。但是,如果你在 filepath.Walk 函数的顶层 defer 语句中关闭文件,那可能不是你想要的,因为 filepath.Walk 可能会在文件真正被关闭之前返回。
9. 创建文件: 在压缩之前,你需要创建目标压缩文件(例如 os.Create("my_document.zip"))。
10. 关闭资源: 务必使用 defer 来确保文件句柄、ZIP 写入器、GZIP 写入器和 TAR 写入器都被正确关闭,以释放资源并确保所有数据都被刷新到文件中。

通过以上示例,你可以根据自己的需求选择使用 archive/ziparchive/tar 来压缩你的文件和目录。

#回复 AI问答 上传/拍照 我的