在Go中压缩文件的方法

696 阅读3分钟

要使用标准库在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) 函数做了以下工作:

  1. 创建一个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() 函数的最后。

  1. 浏览源文件中的所有文件

    // 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()函数走一遍。

  2. 创建一个本地文件头

    // 3. Create a local file header
    header, err := zip.FileInfoHeader(info)
    if err != nil {
    return err
    }
    

    对于档案中的每个文件,我们需要使用函数创建一个本地文件头FileInfoHeader()函数创建一个本地文件头。生成的文件头没有设置压缩方法,所以我们明确将其设置为 zip.Deflate.

  3. 将一个文件的相对路径设为头文件的名称

    // 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中的目录结构。我们通过计算从source path目录到指定文件的相对路径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"
    

    另外,如果该文件是一个目录,应该用尾部的斜线来标记。

  4. 为文件头创建写入器并保存文件的内容

    // 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文件中。作为目录的文件不应该被复制,以免出错。