Golang 學習筆記(五)- archive/zip 實現壓縮及解壓

broqiang發表於2018-06-29

看標準庫文件,就會發現, archive/zip 和 archive/tar 看起來方法名什麼的都很像。使用起來也差不多,如果是按照我的文件順序看到的這篇文件,上一篇 tar 中已經都介紹過了,這裡就不再做過多的說明。

壓縮

和 tar 的過程很像,只有些小的差別,詳情見示例程式碼。

示例程式碼( zip.go ):

package main

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

func main() {
    // 源檔案(準備壓縮的檔案或目錄)
    var src = "log"
    // 目標檔案,壓縮後的檔案
    var dst = "log.zip"

    if err := Zip(dst, src); err != nil {
        log.Fatalln(err)
    }
}

func Zip(dst, src string) (err error) {
    // 建立準備寫入的檔案
    fw, err := os.Create(dst)
    defer fw.Close()
    if err != nil {
        return err
    }

    // 通過 fw 來建立 zip.Write
    zw := zip.NewWriter(fw)
    defer func() {
        // 檢測一下是否成功關閉
        if err := zw.Close(); err != nil {
            log.Fatalln(err)
        }
    }()

    // 下面來將檔案寫入 zw ,因為有可能會有很多個目錄及檔案,所以遞迴處理
    return filepath.Walk(src, func(path string, fi os.FileInfo, errBack error) (err error) {
        if errBack != nil {
            return errBack
        }

        // 通過檔案資訊,建立 zip 的檔案資訊
        fh, err := zip.FileInfoHeader(fi)
        if err != nil {
            return
        }

        // 替換檔案資訊中的檔名
        fh.Name = strings.TrimPrefix(path, string(filepath.Separator))

        // 這步開始沒有加,會發現解壓的時候說它不是個目錄
        if fi.IsDir() {
            fh.Name += "/"
        }

        // 寫入檔案資訊,並返回一個 Write 結構
        w, err := zw.CreateHeader(fh)
        if err != nil {
            return
        }

        // 檢測,如果不是標準檔案就只寫入頭資訊,不寫入檔案資料到 w
        // 如目錄,也沒有資料需要寫
        if !fh.Mode().IsRegular() {
            return nil
        }

        // 開啟要壓縮的檔案
        fr, err := os.Open(path)
        defer fr.Close()
        if err != nil {
            return
        }

        // 將開啟的檔案 Copy 到 w
        n, err := io.Copy(w, fr)
        if err != nil {
            return
        }
        // 輸出壓縮的內容
        fmt.Printf("成功壓縮檔案: %s, 共寫入了 %d 個字元的資料\n", path, n)

        return nil
    })
}

解壓縮

直接看程式碼( unzip.go )吧:

package main

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

func main() {
    // 壓縮包
    var src = "log.zip"
    // 解壓後儲存的位置,為空表示當前目錄
    var dst = ""

    if err := UnZip(dst, src); err != nil {
        log.Fatalln(err)
    }
}

func UnZip(dst, src string) (err error) {
    // 開啟壓縮檔案,這個 zip 包有個方便的 ReadCloser 型別
    // 這個裡面有個方便的 OpenReader 函式,可以比 tar 的時候省去一個開啟檔案的步驟
    zr, err := zip.OpenReader(src)
    defer zr.Close()
    if err != nil {
        return
    }

    // 如果解壓後不是放在當前目錄就按照儲存目錄去建立目錄
    if dst != "" {
        if err := os.MkdirAll(dst, 0755); err != nil {
            return err
        }
    }

    // 遍歷 zr ,將檔案寫入到磁碟
    for _, file := range zr.File {
        path := filepath.Join(dst, file.Name)

        // 如果是目錄,就建立目錄
        if file.FileInfo().IsDir() {
            if err := os.MkdirAll(path, file.Mode()); err != nil {
                return err
            }
            // 因為是目錄,跳過當前迴圈,因為後面都是檔案的處理
            continue
        }

        // 獲取到 Reader
        fr, err := file.Open()
        if err != nil {
            return err
        }

        // 建立要寫出的檔案對應的 Write
        fw, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, file.Mode())
        if err != nil {
            return err
        }

        n, err := io.Copy(fw, fr)
        if err != nil {
            return err
        }

        // 將解壓的結果輸出
        fmt.Printf("成功解壓 %s ,共寫入了 %d 個字元的資料\n", path, n)

        // 因為是在迴圈中,無法使用 defer ,直接放在最後
        // 不過這樣也有問題,當出現 err 的時候就不會執行這個了,
        // 可以把它單獨放在一個函式中,這裡是個實驗,就這樣了
        fw.Close()
        fr.Close()
    }
    return nil
}

本文來自 BroQiang 部落格 隨意轉載及使用,別忘了帶上我。

相關文章