要使用标准库在Go中压缩一个文件或一个目录,使用 zip.Writer类型从 archive/zip包中的类型。使用这种方法创建一个压缩档案,需要浏览所有你想包含的文件,为每个文件生成一个本地文件头,并将其内容写入生成的ZIP文件中。
package main
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
)
func zipSource(source, target string) error {
// 1. Create a ZIP file and zip.Writer
f, err := os.Create(target)
if err != nil {
return err
}
defer f.Close()
writer := zip.NewWriter(f)
defer writer.Close()
// 2. Go through all the files of the source
return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 3. Create a local file header
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// set compression
header.Method = zip.Deflate
// 4. Set relative path of a file as the header name
header.Name, err = filepath.Rel(filepath.Dir(source), path)
if err != nil {
return err
}
if info.IsDir() {
header.Name += "/"
}
// 5. Create writer for the file header and save content of the file
headerWriter, err := writer.CreateHeader(header)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(headerWriter, f)
return err
})
}
func main() {
if err := zipSource("testFolder", "testFolder.zip"); err != nil {
log.Fatal(err)
}
if err := zipSource("testFolder/1/1.1/1.1.txt", "1.1.zip"); err != nil {
log.Fatal(err)
}
}
在上面的例子中,我们使用一个样本目录
testFolder。testFolder ├── 1 │ └── 1.1 │ └── 1.1.txt ├── 2 └── test.txt
zipSource(source, target string) 函数做了以下工作:
-
创建一个ZIP文件和
zip.Writer// 1. Create a ZIP file and zip.Writer f, err := os.Create(target) if err != nil { return err } defer f.Close()
在第一步中,我们需要创建一个ZIP文件并初始化 zip.Writer将压缩数据写入该文件。注意,我们把关闭文件和写入器推迟到zipSource() 函数的最后。
-
浏览源文件中的所有文件
// 2. Go through all the files of the source return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { ... })我们希望
zipSource()函数既能在单个文件上工作,也能在文件夹上工作,所以我们需要使用source路径的每个文件的filepath.Walk()函数走一遍。 -
创建一个本地文件头
// 3. Create a local file header header, err := zip.FileInfoHeader(info) if err != nil { return err }对于档案中的每个文件,我们需要使用函数创建一个本地文件头。
FileInfoHeader()函数创建一个本地文件头。生成的文件头没有设置压缩方法,所以我们明确将其设置为zip.Deflate. -
将一个文件的相对路径设为头文件的名称
// 4. Set relative path of a file as the header name header.Name, err = filepath.Rel(filepath.Dir(source), path) if err != nil { return err } if info.IsDir() { header.Name += "/" }在上一步中,我们用以下函数创建了一个文件头
FileInfoHeader(fi fs.FileInfo)函数创建了一个文件头,其名称来自fs.FileInfo参数。然而,这只是一个基础名称,所以我们需要修改它以保留生成的ZIP中的目录结构。我们通过计算从sourcepath目录到指定文件的相对路径path,使用filepath.Dir()和filepath.Rel()函数。例如,将/a/b/c目录与/a/b/c/d/test.txt文件进行压缩,我们就可以得到。filepath.Rel(filepath.Dir("/a/b/c"), "/a/b/c/d/test.txt")这相当于。
filepath.Rel("/a/b/", "/a/b/c/d/test.txt")结果是,我们得到了相对路径。
"c/d/test.txt"另外,如果该文件是一个目录,应该用尾部的斜线来标记。
-
为文件头创建写入器并保存文件的内容
// 5. Create a writer for the file header and save the content of the file headerWriter, err := writer.CreateHeader(header) if err != nil { return err } if info.IsDir() { return nil } f, err := os.Open(path) if err != nil { return err } defer f.Close()最后一步是根据文件头为指定的文件数据创建一个写入器。该数据被传输到ZIP文件中,使用
io.Copy()函数将数据传输到ZIP文件中。作为目录的文件不应该被复制,以免出错。