【问题标题】:How can I use the "compress/gzip" package to gzip a file?如何使用“compress/gzip”包来 gzip 文件?
【发布时间】:2013-06-03 05:46:06
【问题描述】:

我是 Go 新手,不知道如何使用 compress/gzip 包来发挥我的优势。基本上,我只是想写一些东西到一个文件中,gzip 它并通过另一个脚本直接从压缩格式中读取它。如果有人能给我一个关于如何做到这一点的例子,我将不胜感激。

【问题讨论】:

  • @Nicolai 感谢您编辑问题。

标签: file-io compression go gzip


【解决方案1】:

所有压缩包都实现相同的接口。你会使用这样的东西来压缩:

var b bytes.Buffer
w := gzip.NewWriter(&b)
w.Write([]byte("hello, world\n"))
w.Close()

然后解压:

r, err := gzip.NewReader(&b)
io.Copy(os.Stdout, r)
r.Close()

【讨论】:

  • 似乎总是关闭流的好习惯。也不是只有在关闭时才刷新数据吗?
【解决方案2】:

将任何接口类型的 Go 对象压缩为输入

func compress(obj interface{}) ([]byte, error) {
    var b bytes.Buffer
    objBytes, err := json.Marshal(obj)
    if err != nil {
        return nil, err
    }
    gz := gzip.NewWriter(&b)
    defer gz.Close() //NOT SUFFICIENT, DON'T DEFER WRITER OBJECTS
    if _, err := gz.Write(objBytes); err != nil {
        return nil, err
    }
    // NEED TO CLOSE EXPLICITLY
    if err := gz.Close(); err != nil {
        return nil, err
    }
    return b.Bytes(), nil
}

要解压,

func decompress(obj []byte) ([]byte, error) {
    r, err := gzip.NewReader(bytes.NewReader(obj))
    if err != nil {
        return nil, err
    }
    defer r.Close()
    res, err := ioutil.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return res, nil
}

注意,如果写入后不关闭 Writer 对象,ioutil.ReadAll(r) 将返回 io.EOFio.ErrUnexpectedEOF。我假设 Close() 上的 defer 会正确关闭对象,但不会。 不要推迟 writer 对象。

【讨论】:

  • 你能解释一下“不要推迟写对象”是什么意思吗?在使用defer gz.Close() 时,我从我的输出文件中收到“数据意外结束”消息。把它移到最后解决了我的问题。但我不知道为什么。
  • 如果您只推迟关闭 writer 对象,您将忽略它的返回值,这可能会忽略错误对象。更多参考。 joeshaw.org/dont-defer-close-on-writable-files
  • 我的问题最终是一个新手的错误,我已经回答了here。但我很高兴你分享了那篇文章,谢谢!
【解决方案3】:

与 Laurent 的答案几乎相同,但文件 io:

import (
  "bytes"
  "compress/gzip"
  "io/ioutil"
)
// ...
var b bytes.Buffer
w := gzip.NewWriter(&b)
w.Write([]byte("hello, world\n"))
w.Close() // You must close this first to flush the bytes to the buffer.
err := ioutil.WriteFile("hello_world.txt.gz", b.Bytes(), 0666)

【讨论】:

  • 您应该只使用 os.Open 创建文件并将其传递给 gzip.NewWriter 并且每次写入该 gzip 写入器都会将压缩数据写入该文件。
  • @Giacomo:您应该将其发布为您自己的答案。
【解决方案4】:

对于读取部分,类似于 .gz 文件的有用 ioutil.ReadFile 可能是:

func ReadGzFile(filename string) ([]byte, error) {
    fi, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer fi.Close()

    fz, err := gzip.NewReader(fi)
    if err != nil {
        return nil, err
    }
    defer fz.Close()

    s, err := ioutil.ReadAll(fz)
    if err != nil {
        return nil, err
    }
    return s, nil   
}

【讨论】:

    【解决方案5】:

    这里是解压gzip文件到目标文件的函数:

    func UnpackGzipFile(gzFilePath, dstFilePath string) (int64, error) {
        gzFile, err := os.Open(gzFilePath)
        if err != nil {
            return 0, fmt.Errorf("Failed to open file %s for unpack: %s", gzFilePath, err)
        }
        dstFile, err := os.OpenFile(dstFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
        if err != nil {
            return 0, fmt.Errorf("Failed to create destination file %s for unpack: %s", dstFilePath, err)
        }
    
        ioReader, ioWriter := io.Pipe()
    
        go func() { // goroutine leak is possible here
            gzReader, _ := gzip.NewReader(gzFile)
            // it is important to close the writer or reading from the other end of the
            // pipe or io.copy() will never finish
            defer func(){
                gzFile.Close()
                gzReader.Close()
                ioWriter.Close()
            }()
    
            io.Copy(ioWriter, gzReader)
        }()
    
        written, err := io.Copy(dstFile, ioReader)
        if err != nil {
            return 0, err // goroutine leak is possible here
        }
        ioReader.Close()
        dstFile.Close()
    
        return written, nil
    }
    

    【讨论】:

    • 我正在尝试了解解决方案,您能详细说明为什么我们需要 io.Pipe 吗?
    • @Sairam io.Pipe 允许读取源、gzip 源字节并即时写入目标,而无需为源字节和解压字节分配额外内存。
    【解决方案6】:

    我决定结合其他答案的想法,只提供一个完整的示例程序。显然,有许多不同的方法可以做同样的事情。这只是一种方式:

    package main
    
    import (
        "compress/gzip"
        "fmt"
        "io/ioutil"
        "os"
    )
    
    var zipFile = "zipfile.gz"
    
    func main() {
        writeZip()
        readZip()
    }
    
    func writeZip() {
        handle, err := openFile(zipFile)
        if err != nil {
            fmt.Println("[ERROR] Opening file:", err)
        }
    
        zipWriter, err := gzip.NewWriterLevel(handle, 9)
        if err != nil {
            fmt.Println("[ERROR] New gzip writer:", err)
        }
        numberOfBytesWritten, err := zipWriter.Write([]byte("Hello, World!\n"))
        if err != nil {
            fmt.Println("[ERROR] Writing:", err)
        }
        err = zipWriter.Close()
        if err != nil {
            fmt.Println("[ERROR] Closing zip writer:", err)
        }
        fmt.Println("[INFO] Number of bytes written:", numberOfBytesWritten)
    
        closeFile(handle)
    }
    
    func readZip() {
        handle, err := openFile(zipFile)
        if err != nil {
            fmt.Println("[ERROR] Opening file:", err)
        }
    
        zipReader, err := gzip.NewReader(handle)
        if err != nil {
            fmt.Println("[ERROR] New gzip reader:", err)
        }
        defer zipReader.Close()
    
        fileContents, err := ioutil.ReadAll(zipReader)
        if err != nil {
            fmt.Println("[ERROR] ReadAll:", err)
        }
    
        fmt.Printf("[INFO] Uncompressed contents: %s\n", fileContents)
    
        // ** Another way of reading the file **
        //
        // fileInfo, _ := handle.Stat()
        // fileContents := make([]byte, fileInfo.Size())
        // bytesRead, err := zipReader.Read(fileContents)
        // if err != nil {
        //     fmt.Println("[ERROR] Reading gzip file:", err)
        // }
        // fmt.Println("[INFO] Number of bytes read from the file:", bytesRead)
    
        closeFile(handle)
    }
    
    func openFile(fileToOpen string) (*os.File, error) {
        return os.OpenFile(fileToOpen, openFileOptions, openFilePermissions)
    }
    
    func closeFile(handle *os.File) {
        if handle == nil {
            return
        }
    
        err := handle.Close()
        if err != nil {
            fmt.Println("[ERROR] Closing file:", err)
        }
    }
    
    const openFileOptions int = os.O_CREATE | os.O_RDWR
    const openFilePermissions os.FileMode = 0660
    

    拥有这样的完整示例应该有助于将来参考。

    【讨论】:

    • 我不认为这是一个很好的例子; openFile 应该返回 error; Stat 没有意义(也可能是 nil 值!);来自WriteClose 的任何错误都将被忽略;等等等等。
    • 是的,错误处理可能会加强一点,但它似乎确实是对所提问题的有效答案。 @DaveC
    • @DaveC,在查看了代码并考虑了您的建议后,我做了一些重写。这只是一个例子,但希望编辑改进了最佳实践。感谢您的反馈。
    猜你喜欢
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 2020-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多