首页  >  文章  >  后端开发  >  Go 如何打包与压缩文件

Go 如何打包与压缩文件

Go语言进阶学习
Go语言进阶学习转载
2023-07-21 11:14:381123浏览
文件的打包、压缩与解压缩是经常会使用到的功能,我们可以通过 tar、gzip 等工具来完成这些操作。在 Go 中,标准库<code style='letter-spacing: 1px; word-spacing: 3px; text-align: left; font-size: 14px; overflow-wrap: break-word; padding: 2px 4px; border-radius: 4px; margin: 0px 2px; font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace; word-break: break-all; background: rgba(14, 210, 247, 0.15);'><span style="font-size: 15px;">archive</span>archive<span style="font-size: 15px;">compress</span><span style='letter-spacing: 1px; word-spacing: 3px; font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif; text-align: left; font-size: 15px;'>compress</span>
为我们提供了这些能力,通过本文示例,你会发现以 Go 编程的方式生成与处理压缩打包文件也非常简单。

打包和压缩

在开始代码之前,我们需要明确打包和压缩的概念。
  • 打包,又被称为归档,指的是一个文件或目录的集合,而这个集合被存储在一个文件中。
  • 压缩,指的是利用算法将文件进行处理,以达到保留最大文件信息,而让文件体积变小的目的。

以打包工具 tar 为例,通过其打出来的文件通常称为 tar 包,其文件命名通常以 .tar 结尾。再通过其他的压缩工具对 tar 包进行压缩,例如 gzip 压缩,则得到通常以 .tar.gz 结尾命名的压缩文件(在 tar 中可使用 -z 参数来调用gzip)。

tar 包是文件的集合,其结构也是由数据段组成的,每块数据段包含了文件头(描述文件的元信息)和文件内容。

+----------------------------------------+
| Header                                 |
| [name][mode][owner][group][size]  ...  |
+----------------------------------------+
| Content                                |
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
+----------------------------------------+
| Header                                 |
| [name][mode][owner][group][size]  ...  |
+----------------------------------------+
| Content                                |
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
+----------------------------------------+
| ...                                     |

archive 库打包与解包

archive 库的中文含义是档案,它的作用就是归档(打包)与拆档(解包)。其提供两种方案:tar 与 zip,调用路径分别为<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #000000;background: rgba(14, 210, 247, 0.15);"><span style="font-size: 15px;">archive/tar</span>archive/tar<span style="font-size: 15px;">archive/zip</span><span style="font-size: 15px;">archive/zip</span>

我们以 tar 为例,来展示如何实现文件的打包与解包。

首先,新建目标打包文件为 out.tar,再构造一些文件数据 readme.txt、gopher.txt 和 todo.txt 用于归档。
import (
 "archive/tar"
  ...
)
func main() {
 // Create and add some files to the archive.
 tarPath := "out.tar"
 tarFile, err := os.Create(tarPath)
 if err != nil {
  log.Fatal(err)
 }
 defer tarFile.Close()
 tw := tar.NewWriter(tarFile)
 defer tw.Close()
 var files = []struct {
  Name, Body string
 }{
  {"readme.txt", "This archive contains some text files."},
  {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
  {"todo.txt", "Get animal handling license."},
 }
 ... 
}

接着依次构建文件头信息,分别指定了文件名、权限和大小(可定义更多文件头字段),再通过<span style="font-size: 15px;">*tar.Writer</span>类型的 tw 变量,按序调用<span style="font-size: 15px;">WriteHeader</span><span style="font-size: 15px;">Write</span>方法将需要打包的数据段(文件头+文件内容)写入到out.tar文件。

 ...
 for _, file := range files {
  hdr := &tar.Header{
   Name: file.Name,
   Mode: 0600,
   Size: int64(len(file.Body)),
  }
  if err := tw.WriteHeader(hdr); err != nil {
   log.Fatal(err)
  }
  if _, err := tw.Write([]byte(file.Body)); err != nil {
   log.Fatal(err)
  }
 }
}

执行以上代码,将得到打包后的 out.tar 文件,可通过 tar 工具指定 -tvf 参数查看归档信息。

$ tar -tvf out.tar
-rw-------  0 0      0          38 Jan  1  1970 readme.txt
-rw-------  0 0      0          35 Jan  1  1970 gopher.txt
-rw-------  0 0      0          28 Jan  1  1970 todo.txt

可以看到,指定的文件信息(文件名、权限和大小)符合预期,但其他未指定的元信息是有误的,例如日期(直接给的默认值)。

如果通过 tar 工具,我们可以执行以下命令来提取 out.tar 中的文件。

$ tar -xvf out.tar
x readme.txt
x gopher.txt
x todo.txt

但在程序中实现,应该怎么做呢?

func main() {
 tarPath := "out.tar"
 tarFile, err := os.Open(tarPath)
 if err != nil {
  log.Fatal(err)
 }
 defer tarFile.Close()
 tr := tar.NewReader(tarFile)
 for {
  hdr, err := tr.Next()
  // End of archive
  if err == io.EOF {
   break
  }
  if err != nil {
   log.Fatal(err)
  }
  fmt.Printf("Contents of %s: ", hdr.Name)
  if _, err := io.Copy(os.Stdout, tr); err != nil {
   log.Fatal(err)
  }
  fmt.Println()
 }
}

// Output:
Contents of readme.txt: This archive contains some text files.
Contents of gopher.txt: Gopher names:
George
Geoffrey
Gonzo
Contents of todo.txt: Get animal handling license.

首先需要打开 out.tar,并构造<span style="font-size: 15px;">*tar.Reader</span>类型的 tr 变量。之后,利用<span style="font-size: 15px;">tr.Next</span>依次提取每个数据段内容,并通过 io.Copy(os.Stdout, tr),将文件内容拷贝至标准输出。直到<span style="font-size: 15px;">tr.Next</span>遇到<span style="font-size: 15px;">io.EOF</span>,它代表读取到了归档文件末尾,则退出提取。

compress 库压缩与解压缩

compress 库中支持了多种压缩方案,包括 bzip2、flate、gzip、lzw 和 zlib,调用路径为<span style="font-size: 15px;">compress/xxx</span>

我们以常用的 gzip 为例,来展示压缩与解压缩代码。

如果同样是上文中的文件数据 readme.txt、gopher.txt 和 todo.txt,我们想得到 tar 归档且被压缩了的 out.tar.gz 文件,应该如何做呢?

package main

import (
 "archive/tar"
 "compress/gzip"
 ...
)

func main() {
 tarPath := "out.tar.gz"
 tarFile, err := os.Create(tarPath)
 if err != nil {
  log.Fatal(err)
 }
 defer tarFile.Close()
 gz := gzip.NewWriter(tarFile)
 defer gz.Close()
 tw := tar.NewWriter(gz)
 defer tw.Close()
 ...
}

非常简单!只需要将<span style="font-size: 15px;">tar.NewWriter(tarFile)</span>改为<span style="font-size: 15px;">tar.NewWriter(gz)</span>即可,其中<span style="font-size: 15px;">gz</span>是由<span style="font-size: 15px;">gzip.NewWriter(tarFile)</span> 而来。

我们比较有压缩与无压缩的归档 tar 包大小,可以看到文件体积从4.0K压缩为了224B。

$ ls -alh out.tar out.tar.gz
-rw-r--r--  1 slp  staff   4.0K Jul  3 21:52 out.tar
-rw-r--r--  1 slp  staff   224B Jul  3 21:53 out.tar.gz

同理,如果要解压并解包 out.tar.gz 文件,应该如何做呢?

package main

import (
 "archive/tar"
 "compress/gzip"
  ...
)

func main() {
 tarPath := "out.tar.gz"
 tarFile, err := os.Open(tarPath)
 if err != nil {
  log.Fatal(err)
 }
 defer tarFile.Close()
 gz, err := gzip.NewReader(tarFile)
 if err != nil {
  log.Fatal(err)
 }
  defer gz.Close()
 tr := tar.NewReader(gz)
  ...
}

依然很简单!只需要将<span style="font-size: 15px;">tar.NewReader(tarFile)</span>改为<span style="font-size: 15px;">tar.NewReader(gz)</span>即可,其中<span style="font-size: 15px;">gz</span>是由<span style="font-size: 15px;">gzip.NewReader(tarFile)</span> 而来。

总结

本文展示了如何通过archive/tar包实现文件的打包与解包操作,如何通过<span style="font-size: 15px;">compress/gzip</span>包对tar包开展进一步的压缩与解压缩。

在展示<span style="font-size: 15px;">compress/gzip</span>使用时,多封装一层Writer/Reader,即可为tar归档文件增加压缩与解压缩功能。更棒的是,如果你想切换打包/解包、压缩/解压缩策略,仅仅替换掉对应的 Writer/Reader 即可。而这种便利,源于 Go 优秀的流式 IO 设计。

当然,纸上得来终觉浅,绝知此事要躬行。没有使用过<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #000000;background: rgba(14, 210, 247, 0.15);"><span style="font-size: 15px;">archive</span>archive<span style="font-size: 15px;">compress</span><span style="font-size: 15px;">compress</span>

库的读者,可以尝试用本文未使用过的方案,来试着处理打包压缩文件。

以上是Go 如何打包与压缩文件的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文转载于:Go语言进阶学习。如有侵权,请联系admin@php.cn删除