Go 如何打包與壓縮文件
文件的打包、壓縮與解壓縮是經常會使用到的功能,我們可以通過 tar、gzip 等工具來完成這些操作。在 Go 中,標準庫archive
與compress
爲我們提供了這些能力,通過本文示例,你會發現以 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,調用路徑分別爲archive/tar
和archive/zip
。
我們以 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."},
}
...
}
接着依次構建文件頭信息,分別指定了文件名、權限和大小(可定義更多文件頭字段),再通過*tar.Writer
類型的 tw 變量,按序調用WriteHeader
和Write
方法將需要打包的數據段(文件頭 + 文件內容)寫入到 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,並構造*tar.Reader
類型的 tr 變量。之後,利用tr.Next
依次提取每個數據段內容,並通過 io.Copy(os.Stdout, tr),將文件內容拷貝至標準輸出。直到tr.Next
遇到io.EOF
,它代表讀取到了歸檔文件末尾,則退出提取。
compress 庫壓縮與解壓縮
compress 庫中支持了多種壓縮方案,包括 bzip2、flate、gzip、lzw 和 zlib,調用路徑爲compress/xxx
。
我們以常用的 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()
...
}
非常簡單!只需要將tar.NewWriter(tarFile)
改爲tar.NewWriter(gz)
即可,其中gz
是由gzip.NewWriter(tarFile)
而來。
我們比較有壓縮與無壓縮的歸檔 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)
...
}
依然很簡單!只需要將tar.NewReader(tarFile)
改爲tar.NewReader(gz)
即可,其中gz
是由gzip.NewReader(tarFile)
而來。
總結
本文展示瞭如何通過archive/tar
包實現文件的打包與解包操作,如何通過compress/gzip
包對 tar 包開展進一步的壓縮與解壓縮。
在展示compress/gzip
使用時,多封裝一層 Writer/Reader,即可爲 tar 歸檔文件增加壓縮與解壓縮功能。更棒的是,如果你想切換打包 / 解包、壓縮 / 解壓縮策略,僅僅替換掉對應的 Writer/Reader 即可。而這種便利,源於 Go 優秀的流式 IO 設計。
當然,紙上得來終覺淺,絕知此事要躬行。沒有使用過archive
和compress
庫的讀者,可以嘗試用本文未使用過的方案,來試着處理打包壓縮文件。
機器鈴砍菜刀
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/XSxQL4VYYAP6LBTHMQuhwQ