如何在 Go 中用 gzipped 版本替换文件变量?

How to replace file variable with gzipped version in Go?

我有以下 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

你的代码中有不少错误。

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

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

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

下一步,当你打开一个 io.Closer (*os.File 是一个 io.Closer) 的东西时,确保你用 Close() 方法关闭它,最好作为延迟语句来完成。

接下来,*os.File 是一个 io.Reader, but that is not the same thing as a byte slice []byte. An io.Reader may be used to read bytes into a byte slice. Use io.Copy() 将内容从文件复制到 gzip 流(最终将在缓冲区中)。

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

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

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

最后,由于 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: 如果您不想 "work" 使用压缩数据,但只想发送它,例如作为网络响应,您甚至不需要 bytes.Buffer, you can just "stream" the compressed data to the 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
    }
}

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