【问题标题】:Convert int array to byte array, compress it then reverse it将int数组转换为字节数组,压缩然后反转
【发布时间】:2019-11-18 21:08:20
【问题描述】:

我有一个大的 int 数组,我想在文件系统上保留它。我的理解是存储此类内容的最佳方式是使用gob 包将其转换为字节数组,然后使用gzip 对其进行压缩。 当我再次需要它时,我会反转这个过程。我很确定我正确地存储了它,但是用 EOF 恢复它失败了。长话短说,我在下面有一些示例代码来演示这个问题。 (此处为游乐场链接https://play.golang.org/p/v4rGGeVkLNh)。 我不相信 gob 是必需的,但是阅读它似乎将它存储为字节数组比 int 数组更有效,但这可能不是真的。谢谢!

package main

import (
    "bufio"
    "bytes"
    "compress/gzip"
    "encoding/gob"
    "fmt"
)

func main() {
    arry := []int{1, 2, 3, 4, 5}
    //now gob this
    var indexBuffer bytes.Buffer
    writer := bufio.NewWriter(&indexBuffer)
    encoder := gob.NewEncoder(writer)
    if err := encoder.Encode(arry); err != nil {
        panic(err)
    }
    //now compress it
    var compressionBuffer bytes.Buffer
    compressor := gzip.NewWriter(&compressionBuffer)
    compressor.Write(indexBuffer.Bytes())
    defer compressor.Close()
    //<--- I think all is good until here

    //now decompress it
    buf := bytes.NewBuffer(compressionBuffer.Bytes())
    fmt.Println("byte array before unzipping: ", buf.Bytes())
    if reader, err := gzip.NewReader(buf); err != nil {
        fmt.Println("gzip failed ", err)
        panic(err)
    } else {
        //now ungob it...
        var intArray []int
        decoder := gob.NewDecoder(reader)
        defer reader.Close()
        if err := decoder.Decode(&intArray); err != nil {
            fmt.Println("gob failed ", err)
            panic(err)
        }
        fmt.Println("final int Array content: ", intArray)
    }
}

【问题讨论】:

  • 那你的问题是什么?只是gob 是否合适或更有效?或者您的代码有什么具体问题?
  • 当我尝试在 Gob->compress->expand->Gob 之后恢复初始 int 数组时,我得到一个 EOF - 即它没有恢复到原始 int 数组

标签: go compression gzip eof gob


【解决方案1】:

您正在使用bufio.Writer,正如其名称所暗示的那样,向其写入缓冲区 个字节。这意味着如果您正在使用它,您必须刷新它以确保缓冲的数据能够到达底层写入器:

writer := bufio.NewWriter(&indexBuffer)
encoder := gob.NewEncoder(writer)
if err := encoder.Encode(arry); err != nil {
    panic(err)
}
if err := writer.Flush(); err != nil {
    panic(err)
}

虽然bufio.Writer 的使用是完全没有必要的,因为您已经在写入内存缓冲区 (bytes.Buffer),所以请跳过它,直接写入 bytes.Buffer(所以您不需要甚至必须冲洗):

var indexBuffer bytes.Buffer
encoder := gob.NewEncoder(&indexBuffer)
if err := encoder.Encode(arry); err != nil {
    panic(err)
}

下一个错误是你如何关闭 gzip 流:

defer compressor.Close()

这种延迟关闭只会在封闭函数(main() 函数)返回时发生,而不是早一秒。但是到那时您已经想读取压缩数据,但它可能仍位于gzip.Writer 的内部缓存中,而不是compressionBuffer,因此您显然无法从compressionBuffer 读取压缩数据。在不使用defer的情况下关闭gzip流:

if err := compressor.Close(); err != nil {
    panic(err)
}

通过这些更改,您的程序运行和输出(在Go Playground 上尝试):

byte array before unzipping:  [31 139 8 0 0 0 0 0 0 255 226 249 223 200 196 200 244 191 137 129 145 133 129 129 243 127 19 3 43 19 11 27 7 23 32 0 0 255 255 110 125 126 12 23 0 0 0]
final int Array content:  [1 2 3 4 5]

附注:buf := bytes.NewBuffer(compressionBuffer.Bytes()) - 这个buf 也完全没有必要,你可以直接开始解码compressionBuffer 本身,你可以从中读取之前写入的数据。

您可能已经注意到,压缩数据比初始压缩数据大得多。有几个原因:encoding/gobcompress/gzip 流都有很大的开销,并且它们(可能)只会在更大的范围内使输入更小(5 个 int 数字不符合此条件)。

请查看相关问题:Efficient Go serialization of struct to disk

对于小数组,也可以考虑variable-length encoding,见binary.PutVarint()

【讨论】:

  • 非常感谢。现在阅读有关刷新的内容,但会将作者从进程中删除。编辑:有没有办法知道压缩何时变得有价值?并且是否需要使用 gob 来保存不值得压缩的 []int?
  • @amlwwalker 当它变得“有价值”时:衡量。这取决于输入。一些输入可能会被更好地压缩。 gob 不是必需的,实际上它只是增加了计算和空间开销。如果您仍想压缩,只需使用encoding/binary 将整数转换为字节(您可以将其写入gzip 流)。另请注意,您不应使用int,因为它的大小取决于架构,而应使用固定大小的整数,例如int32int64
  • 酷,所以摆脱 gob 听起来是个好主意。我想我已经很接近了,它使用编码/二进制在压缩后将其转换回 []int64,这对我来说是失败的——读入 []int64 后我得到一个空数组。有机会看看吗? play.golang.org/p/NRimlw4Udss
  • 嗯问题也许我需要先知道数组的大小? gob可以处理吗?编码/二进制不能? - 编辑 - 是的,这似乎是它。嗯。这是否意味着我需要保留原始 []int64 的长度,还是可以即时“解决”? play.golang.org/p/cayXw7fCcK2
  • @amlwwalker []int64 类型值的长度不固定,因此您必须自己保持长度。 gob 会为您解决这些问题。
猜你喜欢
  • 2015-02-16
  • 2012-03-05
  • 1970-01-01
  • 2017-04-14
  • 2011-03-07
  • 1970-01-01
  • 2011-09-04
  • 1970-01-01
相关资源
最近更新 更多