前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang zip 压缩与解压

Golang zip 压缩与解压

作者头像
恋喵大鲤鱼
发布2022-05-09 11:20:59
3.5K0
发布2022-05-09 11:20:59
举报
文章被收录于专栏:C/C++基础C/C++基础

文章目录

  • 1.压缩
  • 2.解压缩
  • 3.进一步封装
    • 3.1 压缩
    • 3.2 解压
    • 3.3 go-huge-util
  • 参考文献

Go 和许多其他编程语言一样,支持从标准库中直接进行 zip 文件的压缩和解压。在本文中,我们将介绍如何在 Go 中利用标准库包 archive/zip 完成 zip 文件的创建和提取。

1.压缩

第一步:创建一个 zip 基础文件。

zip 文件也是一个文件,我们要做的第一件事是创建为一个简单的文件作为 zip 文件,就像在 Go 中处理任何其他文件一样。使用 os package 的 os.Create() 函数创建一个文件对象。

代码语言:javascript
复制
func Create(name string) (*File, error)

第二步:初始化 zip.Writer。

使用 archive/zip 包中的 zip.NewWriter 函数初始化一个 zip.Writer,用于将数据(文件和目录)写入 zip 文件。

代码语言:javascript
复制
func NewWriter(w io.Writer) *Writer

第三步:使用 zip.Writer.Create 创建一个 io.Writer。

一旦创建完 zip writer 后,便可以使用 zip.Writer.Create 向 zip 文件中添加一个文件或目录。它返回一个待压缩的文件内容应写入的 Writer,文件内容将使用 Deflate 方法进行压缩。

代码语言:javascript
复制
func (w *Writer) Create(name string) (io.Writer, error)

第四步:使用 io.Copy 或 io.Writer.Write 写数据到到 zip 文件。

zip.Writer.Create 函数返回一个 io.Writer ,因此任何文件内容都可以通过流式写入该 writer。使用 io.Copy 或调用 writer 的 Write 方法。

代码语言:javascript
复制
func Copy(dst Writer, src Reader) (written int64, err error)

第五步:使用 zip.Writer.Close 关闭 zip 文件。

将所有文件和目录写入 zip 文件后,可以通过 zip.Writer.Close 方法关闭 zip writer,将所有数据写入指向基础 zip 文件的数据流。注意,它不会关闭指向基础 zip 文件的 Writer。

代码语言:javascript
复制
func (w *Writer) Close() error

下面给出示例代码:

代码语言:javascript
复制
package main

import (
    "archive/zip"
    "fmt"
    "io"
    "os"
)

func main() {
    fmt.Println("creating zip archive...")
    archive, err := os.Create("archive.zip")
    if err != nil {
        panic(err)
    }
    defer archive.Close()
    zipWriter := zip.NewWriter(archive)

    fmt.Println("opening first file...")
    f1, err := os.Open("test.csv")
    if err != nil {
        panic(err)
    }
    defer f1.Close()

    fmt.Println("writing first file to archive...")
    w1, err := zipWriter.Create("csv/test.csv")
    if err != nil {
        panic(err)
    }
    if _, err := io.Copy(w1, f1); err != nil {
        panic(err)
    }

    fmt.Println("opening second file")
    f2, err := os.Open("test.txt")
    if err != nil {
        panic(err)
    }
    defer f2.Close()

    fmt.Println("writing second file to archive...")
    w2, err := zipWriter.Create("txt/test.txt")
    if err != nil {
        panic(err)
    }
    if _, err := io.Copy(w2, f2); err != nil {
        panic(err)
    }
    fmt.Println("closing zip archive...")
    zipWriter.Close()
}

运行输出:

代码语言:javascript
复制
creating zip archive...
opening first file...
writing first file to archive...
opening second file
writing second file to archive...
closing zip archive...

最终的 zip 存档包含预期的文件。

代码语言:javascript
复制
unzip -l archive.zip

Archive:  archive.zip
Length  Date  Time  Name
---------  ---------- ----- ----
57  00-00-1980 00:00 csv/test.csv
12  00-00-1980 00:00 txt/test.txt
--------- -------
69 2 files

2.解压缩

利用标准库 archive/zip 包可以创建 zip 文件,同样地也可以完成对 zip 文件的解压缩。下面让我们看看如何在 Go 中解压 zip 文件。

第一步:使用 zip.OpenReader 打开 zip 文件。

要想解压 zip 文件我们可能需要做的第一件事是打开它。我们可以使用 archive/zip 包提供的函数 zip.OpenReader 来打开 zip 文件,以 io.ReadCloser 的形式返回一个 zip.Reader 的实例。

代码语言:javascript
复制
func OpenReader(name string) (*ReadCloser, error)

第二步:循环访问 zip 中的文件。

zip.OpenReader 返回一个 zip.Reader 实例,其包含一个 zip.File 切片。

代码语言:javascript
复制
type Reader struct {
	File []*File
	Comment string
	// contains filtered or unexported fields
}

第三步:使用 zip.File.Open 方法读取 zip 中文件的内容。

代码语言:javascript
复制
func (f *File) Open() (io.ReadCloser, error)

第四步:使用 io.Copy 或 io.Writer.Write 保存解压后的文件内容。

代码语言:javascript
复制
func Copy(dst Writer, src Reader) (written int64, err error)

第五步:使用 zip.Reader.Close 关闭 zip 文件。

读取完 zip 存档中所有文件的内容并保存到指定位置后,要用接口 ReadCloser 中的方法 Close 关闭文件句柄。

代码语言:javascript
复制
type ReadCloser interface {
	Reader
	Closer
}

// Closer is the interface that wraps the basic Close method.
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
	Close() error
}

下面给出示例,我们尝试解压缩包含 2 个文件的 zip 文件。有一个 txt 文件和一个 csv 文件,它们将被解压缩到选定的输出目录中。

代码语言:javascript
复制
package main

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

func main() {
    dst := "output"
    archive, err := zip.OpenReader("archive.zip")
    if err != nil {
        panic(err)
    }
    defer archive.Close()

    for _, f := range archive.File {
        filePath := filepath.Join(dst, f.Name)
        fmt.Println("unzipping file ", filePath)

        if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) {
            fmt.Println("invalid file path")
            return
        }
        if f.FileInfo().IsDir() {
            fmt.Println("creating directory...")
            os.MkdirAll(filePath, os.ModePerm)
            continue
        }

        if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
            panic(err)
        }

        dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
        if err != nil {
            panic(err)
        }

        fileInArchive, err := f.Open()
        if err != nil {
            panic(err)
        }

        if _, err := io.Copy(dstFile, fileInArchive); err != nil {
            panic(err)
        }

        dstFile.Close()
        fileInArchive.Close()
    }
}

运行输出:

代码语言:javascript
复制
unzipping file  output/csv/test.csv
unzipping file  output/txt/test.txt

3.进一步封装

上面详细讲解了压缩与解压缩的操作,并给出了相应的示例。为了更好的复用上面的代码,下面做了进一步的封装,实现两个压缩与解压缩的函数。

3.1 压缩

代码语言:javascript
复制
// Zip compresses the specified files or dirs to zip archive.
// If a path is a dir don't need to specify the trailing path separator.
// For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
// dir
// |-- foo.txt
// |-- bar.txt
// baz.csv
func Zip(zipPath string, paths ...string) error {
	// create zip file
	if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
		return err
	}
	archive, err := os.Create(zipPath)
	if err != nil {
		return err
	}
	defer archive.Close()

	// new zip writer
	zipWriter := zip.NewWriter(archive)
	defer zipWriter.Close()

	// traverse the file or directory
	for _, srcPath := range paths {
		// remove the trailing path separator if path is a directory
		srcPath = strings.TrimSuffix(srcPath, string(os.PathSeparator))

		// visit all the files or directories in the tree
		err = filepath.Walk(srcPath, func(path string, info fs.FileInfo, err error) error {
			if err != nil {
				return err
			}

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

			// set compression
			header.Method = zip.Deflate

			// set relative path of a file as the header name
			header.Name, err = filepath.Rel(filepath.Dir(srcPath), path)
			if err != nil {
				return err
			}
			if info.IsDir() {
				header.Name += string(os.PathSeparator)
			}

			// create writer for the file header and save content of the file
			headerWriter, err := zipWriter.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
		})
		if err != nil {
			return err
		}
	}
	return nil
}

如将一个目录和一个文件添加到当前目录指定名称的 zip 文件。

代码语言:javascript
复制
func main() {
	err := Zip("archive.zip", "dir", "baz.csv")
	fmt.Println("err is", err)
}

我们可以查看一下生成的 zip 文件的内容。

代码语言:javascript
复制
unzip -l archive.zip

Archive:  archive.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  01-15-2022 20:17   dir/
        0  01-15-2022 20:17   dir/bar.txt
        0  01-15-2022 20:17   dir/foo.txt
        0  01-15-2022 20:30   baz.csv
---------                     -------
        0                     4 files

3.2 解压

代码语言:javascript
复制
// Unzip decompresses a zip file to specified directory.
// Note that the destination directory don't need to specify the trailing path separator.
func Unzip(zipPath, dstDir string) error {
	// open zip file
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		return err
	}
	defer reader.Close()
	for _, file := range reader.File {
		if err := unzipFile(file, dstDir); err != nil {
			return err
		}
	}
	return nil
}

func unzipFile(file *zip.File, dstDir string) error {
	// create the directory of file
	filePath := path.Join(dstDir, file.Name)
	if file.FileInfo().IsDir() {
		if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
			return err
		}
		return nil
	}
	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
		return err
	}

	// open the file
	rc, err := file.Open()
	if err != nil {
		return err
	}
	defer rc.Close()

	// create the file
	w, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer w.Close()

	// save the decompressed file content
	_, err = io.Copy(w, rc)
	return err
}

比如将上面创建的 archive.zip 文件解压到当前目录。

代码语言:javascript
复制
func main() {
	err := Unzip("archive.zip", ".")
	fmt.Println("err is", err)
}

3.3 go-huge-util

以上函数已放置开源库 go-huge-util,可 import 直接使用。

代码语言:javascript
复制
package main

import (
	"fmt"

	huge "github.com/dablelv/go-huge-util"
)

func main() {
	_ := huge.Zip("archive.zip", "dir", "baz.csv")
	_ := huge.Unzip("archive.zip", ".")
}

欢迎大家 Star & PR。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-03-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1.压缩
  • 2.解压缩
  • 3.进一步封装
    • 3.1 压缩
      • 3.2 解压
        • 3.3 go-huge-util
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档