登录
首页 >  Golang >  Go教程

Go语言zip归档文件的读写操作

来源:云海天教程

时间:2023-01-07 11:59:51 428浏览 收藏

本篇文章向大家介绍《Go语言zip归档文件的读写操作》,主要包括文件处理,具有一定的参考价值,需要的朋友可以参考一下。

Go语言的标准库提供了对几种压缩格式的支持,其中包括 gzip,因此 Go 程序可以无缝地读写 .gz 扩展名的 gzip 压缩文件或非 .gz 扩展名的非压缩文件。此外,标准库也提供了读和 写 .zip 文件、tar 包文件 (.tar 和 .tar.gz ),以及读 .bz2 文件(即 .tar .bz2 文件)的功能。

本节我们将主要介绍 zip 归档文件的读写操作。

创建 zip 归档文件

要使用 Zip 包来压缩文件,我们首先必须打开一个用于写的文件,然后创建一个 *zip.Writer 值来往其中写入数据。然后,对于每一个我们希望加入 .zip 归档文件的文件,我们必须读取该文件并将其内容写入 *zip.Writer 中。该 pack 程序使用了 createZip() 和 writeFileToZip() 两个函数以这种方式来创建一个 .zip 文件。

func createZip(filename string, files []string) error { file, err := os.Create(filename) if err != nil { return err } defer file.Close() zipper := zip.NewWriter(file) defer zipper.Close() for _, name := range files { if err := writeFileToZip(zipper, name); err != nil { return err } } return nil}该 createZip() 函数和 writeFileToZip() 函数都比较简短,因此容易让人觉得应该写入一个函数中。这是不明智的,因为在该 for 循环中我们可能打开一个又一个的文件(即 files 切片中的所有文件),从而可能超出操作系统允许的文件打开数上限。这点我们在前面章节中己有简短的阐述。

当然,我们可以在每次迭代中调用。s.File.Close(),而非延迟执行它,但这样做还必须保证程序无论是否出错文件都必须关闭。因此,最为简便而干净的解决方案是,像这里所做的那样,总是创建一个独立的函数来处理每个独立的文件。

func writeFileToZip(zipper *zip.Writer, filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() info, err := file.Stat() if err != nil { return err } header, err := zip.FilelnfoHeader(info) if err != nil { return err } header.name = sanitizedName(filename) writer, err := zipper.CreateHeader(header) if err != nil { return err } _, err = io.Copy(writer, file) return err}首先我们打开需要归档的文件以供读取,然后延迟关闭它。这是我们处理文件的老套路了。

接下来,我们调用 os.File.Stat() 方法来取得包含时间戳和权限标志的 os.Fileinfo 值。然后,我们将该值传给 zip.FileInfoHeader() 函数,该函数返回一个 zip.FileHeader 值,其中保存了时间戳、权限以及文件名。在压缩文件中,我们无需使用与原始文件名一样的文件名,因此这里我们使用净化过的文件名来覆盖原始文件名(保存在 zip.FileHeader.Name 字段中)。

头部设置好之后,我们将其作为参数调用 zip.CreateHeader() 函数。这会在 .zip 压缩文件中创建一个项,其中包含头部的时间戳、权限以及文件名,并返回一个 io.Writer,我们可以往其中写入需要被压缩的文件的内容。

为此,我们使用了 io.Copy() 函数,它能够返回所复制的字节数,以及一个为空或者非空的错误值。

如果在任何时候发生错误,该函数就会立即返回并由调用者处理错误。如果最终没有错误发生,那么该 .zip 压缩文件就会包含该给定文件。

func sanitizedName(filename string) string{ if len(filename) > 1 && filename[1] == ':' && runtime.GOOS == *'windows" { filename = filename[2:] } filename = filepath.ToSlash(filename) filename = strings. TrimLef t( filename, "/.**) return strings.Replace(filename, ".・/", -1)}如果一个归档文件中包含的文件带有绝对路径或者含有“..”路径组件,我们就有可能在解开归档的时候意外覆盖本地重要文件。为了降低这种风险,我们对保存在归档文件里每个文件的文件名都做了相应的净化。

该 sanitizedName() 函数会删除路径头部的盘符以及冒号(如果有的话),然后删除头部任何目录分隔符、点号以及任何路径组件,并将文件分隔符强制转换成正向斜线。

解开 zip 归档文件

解开一个 .zip 归档文件与创建一个归档文件一样简单,只是如果归档文件中包含带有路径的文件名,就必须重建目录结构。

func unpackZip(filename string) error { reader, err := zip.OpenReader(filename) if err != nil { return err } defer reader.Close() for _, zipFile := range reader.Reader.File { name := san it izedName (zipFiJLe.Name) mode := zipFile.Mode() if mode.IsDir() { if err = os.MkdirAl1(name, 0755); err != nil { return err } } else { if err = unpackZippedFile(name, zipFile); err != nil { return err } } } return nil}该函数打开给定的 .zip 文件用于读取。这里没有使用 os.Open() 函数来打开文件后调用 zip.NewReader(),而是使用 zip 包提供的 zip.OpenReader() 函数,它可以方便地打开并返回一个 *zip.ReadCloser 值让我们使用。

zip.ReadCloser 最为重要的一点是它包含了导出的 zip.Reader 结构体字段,其中包含一个包含指向 zip.File 结构体指针的 []*zip.File 切片,其中的每一项表示 .zip 压缩文件中的一个文件。

我们迭代访问该 reader 的 zip.File 结构体,并创建一个净化过的文件及目录名(使用我们在 pack 程序中用到的 sanitizedName() 函数),以降低覆盖重要文件的风险。

如果遇到一个目录(由 *zip.File 的 os.FileMode 的 IsDir() 方法报告),我们就创建一个目录。os.MkdirAll() 函数传入了有用的属性信息,会自动创建必要的中间目录以创建特定的目标目录,如果目录已经存在则会安全地返回 nil 而不执行任何操作。如果遇到的是一个文件,则交由自定义的 unpackZippedFile() 函数进行解压。

func unpackZippedFile(filename string, zipFile *zipFile) error { writer, err := os.Create(filename) if err != nil { return err } defer writer.Close() reader, err := zipFile.Open() if err != nil { return err } defer reader.Close() if _, err = io.Copy(writer, reader); err != nil { return err } if filename == zipFile.Name { fm.Println(filename) } else { fmt.Printf("%s [%s]", filename, zipFile.Name) } return nil}unpackZippedFile() 函数的作用就是将 .zip 归档文件里的单个文件抽取出来,写到 filename 指定的文件里去。首先它创建所需要的文件,然后,使用 zip.File.Open() 函数打开指定的归档文件,并将数据复制到新创建的文件里去。

最后,如果没有错误发生,该函数会往终端打印所创建文件的文件名,如果处理后的文件名与原始文件名不一样,则将原始文件名包含在方括号中。

值得注意的是,该 *zip.File 类型也有一些其他的方法,如 zip.File.Mode()(在前面的 unpackZip() 函数中已有使用),zip.File.ModTime()(以 time.Time 值返回文件的修改时间)以及返回文件的 os.Fileinfo 值的 zip.Fileinfo()。

以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于Golang的相关知识,也可关注golang学习网公众号。

声明:本文转载于:云海天教程 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>