Golang的archive/zip库通过zip.Writer和zip.Reader实现ZIP文件的压缩与解压,压缩时需为每个文件创建zip.FileHeader并写入相对路径以保留目录结构,解压时需校验路径防止Zip Slip漏洞;处理大文件时依赖流式I/O避免内存溢出,但需注意磁盘I/O、CPU消耗及小文件数量带来的性能瓶颈,可通过调整压缩级别、并发处理和缓冲区优化提升效率;常见错误包括文件I/O失败、ZIP格式无效、路径安全风险等,应全面检查错误、包装错误信息、验证路径合法性并确保资源及时释放。

Golang的
archive/zip
使用
archive/zip
zip.Writer
zip.Reader
文件压缩实践:
压缩文件通常涉及创建一个
zip.Writer
zip.FileHeader
立即学习“go语言免费学习笔记(深入)”;
package main
import (
"archive/zip"
"io"
"os"
"path/filepath"
"fmt"
)
// CompressFiles 压缩一个或多个文件到一个ZIP包
func CompressFiles(outputZipPath string, filesToCompress []string) error {
outFile, err := os.Create(outputZipPath)
if err != nil {
return fmt.Errorf("创建ZIP文件失败: %w", err)
}
defer outFile.Close()
zipWriter := zip.NewWriter(outFile)
defer zipWriter.Close() // 确保在函数结束时关闭zipWriter,否则文件可能损坏
for _, filePath := range filesToCompress {
fileInfo, err := os.Stat(filePath)
if err != nil {
// 文件不存在或无法访问,我们选择跳过而不是中断整个压缩过程
fmt.Printf("警告:无法访问文件 %s,跳过。错误:%v\n", filePath, err)
continue
}
// 创建文件头,指定文件在ZIP包中的路径名
// 这里直接使用原始文件名作为ZIP包内的路径,如果需要保留目录结构,则需要更复杂的逻辑
header, err := zip.FileInfoHeader(fileInfo)
if err != nil {
return fmt.Errorf("创建文件头失败 %s: %w", filePath, err)
}
header.Name = filepath.Base(filePath) // 仅保留文件名,不带路径
header.Method = zip.Deflate // 默认使用Deflate压缩方法
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return fmt.Errorf("在ZIP中创建文件 %s 失败: %w", header.Name, err)
}
// 如果是目录,我们通常不直接写入内容,而是在递归压缩时处理
if fileInfo.IsDir() {
// 在这里,如果filePath是目录,我们只是创建了一个空目录项。
// 真正的目录内容压缩需要递归遍历。
continue
}
// 打开源文件并将其内容拷贝到ZIP Writer中
srcFile, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("打开源文件 %s 失败: %w", filePath, err)
}
defer srcFile.Close() // 确保源文件在使用后关闭
_, err = io.Copy(writer, srcFile)
if err != nil {
return fmt.Errorf("拷贝文件内容 %s 失败: %w", filePath, err)
}
}
return nil
}
// CompressDirectory 递归压缩一个目录及其内容到ZIP包
func CompressDirectory(outputZipPath, sourceDirPath string) error {
outFile, err := os.Create(outputZipPath)
if err != nil {
return fmt.Errorf("创建ZIP文件失败: %w", err)
}
defer outFile.Close()
zipWriter := zip.NewWriter(outFile)
defer zipWriter.Close()
// 使用filepath.Walk遍历目录
return filepath.Walk(sourceDirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("遍历文件 %s 失败: %w", path, err)
}
// 计算文件在ZIP包中的相对路径
// sourceDirPath 可能是 "my_folder",path 可能是 "my_folder/sub/file.txt"
// 相对路径就是 "sub/file.txt"
relPath, err := filepath.Rel(sourceDirPath, path)
if err != nil {
return fmt.Errorf("计算相对路径失败 %s: %w", path, err)
}
if relPath == "." { // 根目录本身不需要添加到ZIP中
return nil
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return fmt.Errorf("创建文件头失败 %s: %w", path, err)
}
header.Name = relPath // 使用相对路径作为ZIP包内的名称
header.Method = zip.Deflate
if info.IsDir() {
header.Name += "/" // 确保目录以斜杠结尾
}
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return fmt.Errorf("在ZIP中创建项 %s 失败: %w", header.Name, err)
}
if !info.IsDir() {
srcFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("打开源文件 %s 失败: %w", path, err)
}
defer srcFile.Close()
_, err = io.Copy(writer, srcFile)
if err != nil {
return fmt.Errorf("拷贝文件内容 %s 失败: %w", path, err)
}
}
return nil
})
}文件解压实践:
解压过程是打开一个ZIP文件,然后遍历其内部的
zip.File
// DecompressZip 解压ZIP文件到指定目录
func DecompressZip(zipPath, destDirPath string) error {
readCloser, err := zip.OpenReader(zipPath)
if err != nil {
return fmt.Errorf("打开ZIP文件失败: %w", err)
}
defer readCloser.Close()
// 确保目标目录存在
if err := os.MkdirAll(destDirPath, 0755); err != nil {
return fmt.Errorf("创建目标目录失败 %s: %w", destDirPath, err)
}
for _, file := range readCloser.File {
// 构建目标文件路径,确保路径安全,防止zip slip漏洞
destPath := filepath.Join(destDirPath, file.Name)
if !filepath.HasPrefix(destPath, filepath.Clean(destDirPath)+string(os.PathSeparator)) {
return fmt.Errorf("非法文件路径 %s", file.Name)
}
if file.FileInfo().IsDir() {
// 创建目录
if err := os.MkdirAll(destPath, file.Mode()); err != nil {
return fmt.Errorf("创建目录 %s 失败: %w", destPath, err)
}
continue
}
// 确保文件所在的目录存在
if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
return fmt.Errorf("创建文件父目录 %s 失败: %w", filepath.Dir(destPath), err)
}
outFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return fmt.Errorf("创建目标文件 %s 失败: %w", destPath, err)
}
defer outFile.Close() // 每次循环内部的文件都需要defer关闭
rc, err := file.Open()
if err != nil {
return fmt.Errorf("打开ZIP内部文件 %s 失败: %w", file.Name, err)
}
defer rc.Close() // 每次循环内部的reader都需要defer关闭
_, err = io.Copy(outFile, rc)
if err != nil {
return fmt.Errorf("解压文件内容 %s 失败: %w", file.Name, err)
}
}
return nil
}这确实是
archive/zip
zip.FileHeader.Name
filepath.Base(filePath)
header.Name
要保留原始目录结构,核心思想是计算每个文件相对于你想要压缩的“根目录”的相对路径。例如,如果你想压缩
project/src/main.go
src/main.go
project
filepath.Walk
filepath.Walk
path
具体来说,可以使用
filepath.Rel(sourceDirPath, path)
zip.FileHeader.Name
header.Name
my_dir/
archive/zip
archive/zip
首先,
archive/zip
io.Reader
io.Writer
io.Copy
然而,性能瓶颈可能出现在几个方面:
archive/zip
zip.Writer.SetCompressionLevel
zip.FileHeader
io.Copy
优化策略:
zipWriter.SetCompressionLevel(zip.BestSpeed)
archive/zip
io.Copy
io.CopyBuffer(dst, src, make([]byte, bufferSize))
file.Name
坦白说,对于极致的性能需求,有时可能需要考虑更底层的库或者不同的压缩格式(如
tar.gz
archive/zip
archive/zip
在使用
archive/zip
常见的错误类型:
os.Create
os.Open
io.Copy
os.MkdirAll
zip.OpenReader
zip.Writer.Close()
zip.Writer.Close()
io.Writer
zip.File.Open()
../../../../etc/passwd
有效排查与处理:
error
defer
defer
zipWriter.Close()
defer
defer
fmt.Errorf("描述性信息: %w", err)errors.Is
errors.As
DecompressZip
filepath.HasPrefix
zip.Writer
zip.Reader
defer Close()
defer
总的来说,处理
archive/zip
以上就是Golang archive/zip库ZIP文件压缩与解压实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号