【问题标题】:How to replace file variable with gzipped version in Go?如何在 Go 中用 gzip 压缩版本替换文件变量?
【发布时间】:2016-10-20 15:49:13
【问题描述】:

我有以下 Go 代码:

file, err := os.Open(fileName)
if err != nil {
    fatalf(service, "Error opening %q: %v", fileName, err)
}

// Check if gzip should be applied
if *metaGzip == true {
    var b bytes.Buffer
    w := gzip.NewWriter(&b)
    w.Write(file)
    w.Close()
    file = w
}

如果metaGzip = true,我想将file的文件内容替换为压缩后的版本。

PS:
我遵循了这个建议:Getting "bytes.Buffer does not implement io.Writer" error message 但我仍然收到错误:cannot use file (type *os.File) as type []byte in argument to w.Write

【问题讨论】:

    标签: file go compression gzip


    【解决方案1】:

    您的代码中有很多错误。

    作为“pre-first”,始终检查返回的错误!

    首先,os.Open() 以只读模式打开文件。为了能够替换磁盘上的文件内容,您必须改为以读写模式打开它:

    file, err := os.OpenFile(fileName, os.O_RDWR, 0)
    

    接下来,当您打开 io.Closer*os.Fileio.Closer)的内容时,请确保使用 Close() 方法将其关闭,最好作为延迟语句完成。

    接下来,*os.Fileio.Reader,但这与字节切片 []byte 不同。 io.Reader 可用于将字节读入字节片。使用io.Copy() 将文件中的内容复制到 gzip 流(最终会在缓冲区中)。

    在某些情况下(您不关闭gzip.Writer),您必须调用gzip.Writer.Flush() 以确保所有内容都刷新到其写入器(在这种情况下是缓冲区)。请注意,gzip.Writer.Close() 也会刷新,因此这似乎是一个不必要的步骤,但必须这样做,例如,如果 gzip.WriterClose() 也被称为延迟语句,因为那样它可能不会在我们之前执行使用缓冲区的内容。因为在我们的示例中,我们在io.Copy() 之后关闭了 gzip 编写器,这将处理必要的刷新。

    接下来,要替换原始文件的内容,必须回溯到要替换的文件开头。为此,您可以使用File.Seek()

    接下来,您可以再次使用io.Copy() 将缓冲区的内容(压缩后的数据)复制到文件中。

    最后,由于 gzip 压缩的内容很可能比原始文件的大小短,因此您必须按照 gzip 内容的大小截断文件(否则原始文件的未压缩内容可能会保留在那里)。

    这是完整的代码:

    file, err := os.OpenFile(fileName, os.O_RDWR, 0)
    if err != nil {
        log.Fatalf("Error opening %q: %v", fileName, err)
    }
    defer file.Close()
    
    // Check if gzip should be applied
    if *metaGzip {
        var b = &bytes.Buffer{}
        w := gzip.NewWriter(b)
        if _, err := io.Copy(w, file); err != nil {
            panic(err)
        }
        if err := w.Close(); err != nil { // This also flushes
            panic(err)
        }
        if _, err := file.Seek(0, 0); err != nil {
            panic(err)
        }
        if _, err := io.Copy(file, b); err != nil {
            panic(err)
        }
        if err := file.Truncate(int64(b.Len())); err != nil {
            panic(err)
        }
    }
    

    注意:以上代码将替换您磁盘上的文件内容。如果你不想要这个并且你只需要压缩数据,你可以这样做。请注意,我使用了io.Reader 类型的新input 变量,因为bytes.Buffer(或*bytes.Buffer)的值不能分配给*os.File 类型的变量,我们很可能只需要结果作为io.Reader 的值(这由两者实现):

    var input io.Reader
    
    file, err := os.Open(fileName)
    if err != nil {
        log.Fatalf("Error opening %q: %v", fileName, err)
    }
    defer file.Close()
    
    // Check if gzip should be applied
    if *metaGzip {
        var b = &bytes.Buffer{}
        w := gzip.NewWriter(b)
        if _, err := io.Copy(w, file); err != nil {
            panic(err)
        }
        if err := w.Close(); err != nil { // This also flushes
            panic(err)
        }
        input = b
    } else {
        input = file
    }
    
    // Use input here
    

    注意 #2: 如果您不想“处理”压缩数据,但只想发送它,例如作为网络响应,您甚至不需要bytes.Buffer,您只需将压缩数据“流式传输”到http.ResponseWriter

    它可能看起来像这样:

    func myHandler(w http.ResponseWriter, r *http.Request) {
        file, err := os.Open(fileName)
        if err != nil {
            http.NotFound(w, r)
        }
        defer file.Close()
    
        gz := gzip.NewWriter(w)
        defer gz.Close()
    
        if _, err := io.Copy(gz, file); err != nil {
            // handle error
        }
    }
    

    将自动检测并设置正确的内容类型。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多