Golang Cafe #12 まとめ compressパッケージ

2014/01/12に開催された「Golang Cafe #12」についてのまとめです。
今回はcompressパッケージ以下をみていきました。
サンプルコード+TakashiYokoyama氏が準備してくれています。

基本的な処理の流れは前回と同じなのでまとめが簡単になっているかもしれません。
lzwパッケージzlibパッケージについては、サンプルコードもほぼ同じになる(パッケージ修飾が異なる程度)ので説明は省略されました。

bzip2パッケージ

bzip2パッケージはbzip2形式で圧縮されたファイルを解凍することができます。圧縮することはできません。

func main() {
    var file *os.File
    var err error

    if file, err = os.Open("files/golang.txt.bz2"); err != nil {
        log.Fatalln(err)
    }
    defer file.Close()

    // bzip2はReaderしかない。
    reader := bzip2.NewReader(file)

    io.Copy(os.Stdout, reader)
}
  1. NewReaderメソッドでReaderを作成できます。引数にはio.ReaderをとるのでファイルでなくてもRead(p []byte) (n int, err error)なメソッドを実装しているものなら何でも構いません。
  2. io.Readerなので読み込むか、他のパッケージのメソッドに渡して終了です。

flateパッケージ

flateパッケージはflate形式での圧縮および解凍を行うためのパッケージです。
恥ずかしながらflate形式というのは知らなかったのですが、画像の圧縮等に使用されるもののようです。

圧縮
func main() {
    var file *os.File
    var err error
    var writer *flate.Writer
    var body []byte

    if file, err = os.Create("output/sample.tar.flate"); err != nil {
        log.Fatalln(err)
    }
    defer file.Close()

    // flateはレベルを指定する。flate.BestSpeed or flate.BestCompression or DefaultCompression
    if writer, err = flate.NewWriter(file, flate.BestCompression); err != nil {
        log.Fatalln(err)
    }
    defer writer.Close()

    var filepaths = []string {
        "files/b0044482_1413812.jpg",
        "files/dart_flight_school.png",
        "files/golang.txt",
    }

    tw := tar.NewWriter(writer)
    defer tw.Close()

    for _, filepath := range filepaths {
        if body, err = ioutil.ReadFile(filepath); err != nil {
            log.Fatalln(err)
        }

        if body != nil {
            hdr := &tar.Header {
                Name: path.Base(filepath),
                Size: int64(len(body)),
            }
            if err := tw.WriteHeader(hdr); err != nil {
                println(err)
            }
            if _, err := tw.Write(body); err != nil {
                println(err)
            }
        }
    }
}

Windowsユーザには馴染みが少ないかもしれませんが、このサンプルではtarでまとめたものをflateで圧縮するという2段階の処理を行っていので多少複雑になっています。

  1. flate.NewWriterメソッドWriterを作成します。引数を2つとり、1つ目はio.WriterなのでファイルでなくてもWrite(p []byte) (n int, err error)なメソッドを実装しているものなら何でも構いません。2つ目は圧縮率を指定することができ、-1から9までを指定できるようです。この値はconstantsで定義されています。
  2. 直接Writeメソッドで書きだすこともできますが、このサンプルでは作成したWritertarパッケージのWriterに渡して書き込んでいます。tarパッケージのWriterに渡せる理由と書き出し方法は前回まとめたので省略します。

単純に次へ次へとWriterを渡しているだけなのですが最初「ん?」となるかも知れないので補足的に他の言語で書いてみると、
C#

using (FileStream fs = new FileStream(出力先))
using (Flate flate = new Flate(fs))
using (Tar tar = new Tar(flate))
{
    tar.Write();
}

Java

try (FileOutputStream fos = new FileOutputStream(出力先);
        Flate flate = new Flate(fos);
        Tar tar = new Tar(flate)) {

    tar.write();
}

このような形でしょうか。データ→tar→flate→出力先ファイルという流れです。
※Flate、Tarクラスは説明するために適当に書いているクラスです、実際に存在するかどうかは分かりません。

解凍
func main() {
    var file *os.File
    var err error

    if file, err = os.Open("output/sample.tar.flate"); err != nil {
        log.Fatalln(err)
    }
    defer file.Close()

    tr := tar.NewReader(flate.NewReader(file))

    var header *tar.Header
    for {
        header, err = tr.Next()
        if err == io.EOF {
            // ファイルの最後
            break
        }
        if err != nil {
            log.Fatalln(err)
        }

        buf := new(bytes.Buffer)
        if _, err = io.Copy(buf, tr); err != nil {
            log.Fatalln(err)
        }

        if err = ioutil.WriteFile("output/" + header.Name, buf.Bytes(), 0755); err != nil {
            log.Fatal(err)
        }
    }
}
  1. NewReaderメソッドでReaderを作成します。引数にはio.ReaderをとるのでファイルでなくてもRead(p []byte) (n int, err error)なメソッドを実装しているものなら何でも構いません。
  2. io.Readerを実装しているので、そのまま読み込んでも構わないですが、このサンプルではtar.flateの形式のファイルを読み込むために、tarパッケージのReaderに渡して読み込んでいます。tarパッケージのReaderに渡せる理由と読み込み方法は前回まとめたので省略します。

gzipパッケージ

gzipパッケージgzip形式での圧縮および解凍を行うためのパッケージです。

圧縮
func main() {
    var file *os.File
    var err error
    var writer *gzip.Writer
    var body []byte

    if file, err = os.Create("output/sample.tar.gz"); err != nil {
        log.Fatalln(err)
    }
    defer file.Close()

    // gzip.NewWriter()なら、エラーを返さないので便利
    if writer, err = gzip.NewWriterLevel(file, gzip.BestCompression); err != nil {
        log.Fatalln(err)
    }
    defer writer.Close()

    var filepaths = []string {
        "files/b0044482_1413812.jpg",
        "files/dart_flight_school.png",
        "files/golang.txt",
    }

    tw := tar.NewWriter(writer)
    defer tw.Close()

    for _, filepath := range filepaths {
        if body, err = ioutil.ReadFile(filepath); err != nil {
            log.Fatalln(err)
        }

        if body != nil {
            hdr := &tar.Header {
                Name: path.Base(filepath),
                Size: int64(len(body)),
            }
            if err := tw.WriteHeader(hdr); err != nil {
                println(err)
            }
            if _, err := tw.Write(body); err != nil {
                println(err)
            }
        }
    }
}

このサンプルでは先ほどのflateのサンプルと同じようにtarでまとめたものをgzipで圧縮するという2段階の処理を行っています。
処理の手順についてはgzip.NewWriterLevelに変更になっている程度なので詳細な説明は割愛します。

解凍
func main() {
    var file *os.File
    var err error
    var reader *gzip.Reader

    if file, err = os.Open("output/sample.tar.gz"); err != nil {
        log.Fatalln(err)
    }
    defer file.Close()

    if reader, err = gzip.NewReader(file); err != nil {
        log.Fatalln(err)
    }
    defer reader.Close()

    tr := tar.NewReader(reader)

    var header *tar.Header
    for {
        header, err = tr.Next()
        if err == io.EOF {
            // ファイルの最後
            break
        }
        if err != nil {
            log.Fatalln(err)
        }

        buf := new(bytes.Buffer)
        if _, err = io.Copy(buf, tr); err != nil {
            log.Fatalln(err)
        }

        if err = ioutil.WriteFile("output/" + header.Name, buf.Bytes(), 0755); err != nil {
            log.Fatal(err)
        }
    }
}

解凍についても先ほどのflateのサンプルとほぼ同じで、gzip.NewReaderに変更になっているだけなので説明は省略します。


これ以降、lzwパッケージzlibパッケージもほぼ同じソースコードとなってしまうとのことで省略となりました。

その他

今回のどのパッケージに関してもだと思うのですが、NewReader系メソッド(関数)、NewWriter系メソッドで、2つ目の戻り値でerrorが返ってくるものとそうでないのもがありました。これについては疑問が残ったので(できれば)別エントリでまとめておきたいと思います。
また今回もType assertionsについては議論がありました。これについてはどこかのタイミングで一度しっかりやろうということになりました。

次回

次回は、containerパッケージをみていくそうです。