如何使用“compress/gzip”包对文件进行gzip压缩?

39

我对Go语言不熟悉,无法搞清楚如何利用compress/gzip包。基本上,我只想将一些东西写入文件中,压缩它并通过另一个脚本直接从压缩格式中读取它。如果有人能给我提供一个例子来说明如何操作,我将不胜感激。

6个回答

61

所有压缩包都实现了相同的接口。您可以使用以下方法进行压缩:

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()

似乎关闭流是一个好的实践。而且只有在关闭时才会刷新数据,对吗? - laurent
这需要先将整个内容加载到内存中。对于大文件来说,可能会引起麻烦。 - ahmadkarimi12

11

和Laurent的回答基本相同,但与文件输入/输出相关:

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)

6
您可以使用 os.Open 创建文件,并将其传递给 gzip.NewWriter,此后每次向 gzip writer 写入数据都会将压缩数据写入该文件。 - Giacomo
1
@Giacomo:你应该把那个作为你自己的答案发布。 - user1175849

9

关于“Read”部分,类似于对.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   
}

7
在这里,提供将gzip文件解压到目标文件的函数:
func UnpackGzipFile(gzFilePath, dstFilePath string) (int64, error) {
    gzFile, err := os.Open(gzFilePath)
    if err != nil {
        return 0, fmt.Errorf("open file %q to unpack: %w", gzFilePath, err)
    }
    dstFile, err := os.OpenFile(dstFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
    if err != nil {
        return 0, fmt.Errorf("create destination file %q to unpack: %w", dstFilePath, err)
    }
    defer dstFile.Close()

    ioReader, ioWriter := io.Pipe()
    defer ioReader.Close()

    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
    }

    return written, nil
}

我正在尝试理解这个解决方案,您可以详细说明为什么我们需要在这里使用io.Pipe吗? - Sairam
1
@Sairam io.Pipe 允许在不为源和编码字节分配额外内存的情况下,实时读取源、gzip 源字节并写入目标。 - Oleg Neumyvakin

2

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

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(也可能是空值!);忽略了 WriteClose 的任何错误;等等。 - Dave C
1
是的,错误处理可能需要加强一些,但它似乎是对所提出问题的一个有效回答。@DaveC - Michael Whatcott
1
@DaveC,在审查了代码并考虑了您的建议后,我进行了一些重写。这只是一个例子,但希望这次编辑已经改进了最佳实践。感谢您的反馈。 - SunSparc

1
压缩任何输入的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
}

注意,ioutil.ReadAll(r)会在写入后如果你不关闭Writer对象则返回io.EOFio.ErrUnexpectedEOF。我曾认为使用defer on Close()可以正确关闭对象,但实际上并不能。不要使用defer关闭writer对象。

你能解释一下“不要延迟写入对象”的意思吗?在使用defer gz.Close()时,我从我的输出文件中收到了一个“意外的数据结尾”消息。将其移动到最后解决了我的问题。但我不知道为什么。 - rideron89
1
如果你只是推迟了对写入对象的关闭操作,那么你就忽略了它的返回值,这可能会忽略一个错误对象。更多信息请参考 https://www.joeshaw.org/dont-defer-close-on-writable-files/。 - Riya John
1
我的问题最终是一个新手错误,我在这里得到了答案(https://dev59.com/Han2oIgBc1ULPQZFGdM2)。但我很高兴你分享了那篇文章,谢谢! - rideron89

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接