【问题标题】:Issue in Go with appending to []byte, writing to file, and reading itGo中的问题是附加到[]字节,写入文件并读取它
【发布时间】:2019-12-23 01:02:10
【问题描述】:

我正在尝试解析大量 IP(约 20mb 或 400 万个 IP),将它们作为字节存储在文件中,然后再读取它们。

我遇到的问题是我希望它们按排序顺序存储,但我看到随机字节切片在读回它们时看起来像是损坏的 IP。

// 让这个叫做 generator.go

var buf []byte


// So this is where we build up `buf`, which we later write to a file.
func writeOut(record RecordStruct) {
    // This line is never hit. All slices have a length of 4, as expected
    if len(record.IPEnd.Bytes()) != 4 {
        fmt.Println(len(record.IPEnd.Bytes()), record.IPEnd.Bytes())
    }

    // Let's append the IP to the byte slice with a seperater of 10 null bytes which we will later call bytes.Split on.
    buf = append(buf, append(record.IPEnd.Bytes(), bytes.Repeat([]byte{0}, 10)...)...)
}

func main () {
    // Called many times. For brevity I won't include all of that logic. 
    // There are no Goroutines in the code and running with -race says all is fine.

    writeOut(...)

    err := ioutil.WriteFile("bin/test", buf, 0644)
}

reader.go

func main() {
    bytez, err := ioutil.ReadFile("bin/test")

    if err != nil {
        fmt.Println("Asset was not found.")
    }

    haystack := bytes.Split(bytez, bytes.Repeat([]byte{0}, 10))

    for _, needle := range haystack {
        // Get's hit maybe 10% of the time. The logs are below.
        if len(needle) != 4 {
            fmt.Println(fmt.Println(needle))
        }
    }
}
[188 114 235]
14 <nil>
[120 188 114 235 121]
22 <nil>
[188 148 98]
13 <nil>
[120 188 148 98 121]
21 <nil>

如您所见,IP 位太少或太多。

如果我更改日志以更好地说明问题,看起来最后一个八位字节溢出了?

Fine: [46 36 202 235]
Fine: [46 36 202 239]
Fine: [46 36 202 255]
Weird: [46 36 203]
Weird: [0 46 36 203 1]
Fine: [46 36 203 3]
Fine: [46 36 203 5]
Fine: [46 36 203 7]
Fine: [46 36 203 9]

【问题讨论】:

  • 每次输出(损坏的条目)都一样吗?
  • 等等,你的任何条目都是以 0 开头还是结尾?
  • IP 地址以零字节结尾时,代码没有正确拆分字节。
  • @CeriseLimón 我认为你在那里有所作为。所有这些日志都遵循这种模式。有好的解决办法吗?
  • Convert all address to 16 byte representation 并存储为没有分隔符的 16 字节记录。

标签: go byte slice


【解决方案1】:

当 IP 地址以零字节结尾时,代码不会正确拆分字节。通过converting the address to 16 byte representation 修复并存储没有分隔符的 16 字节记录。

您可以使用以下方法有效地将 v4 和 v6 地址的混合附加到缓冲区:

switch len(p) {
case net.IPv6len: 
    buf = append(buf, p...)
case net.IPv4len:
    buf = append(buf, v4InV6Prefix...)
    buf = append(buf, p...)
default:
    // handle error
}

其中v4InV6Prefix 是一个包级变量,其值为[]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}

将文件读取为 v6 地址:

 buf, err := ioutil.ReadFile(xxx)
 if err != nil {
     // handle error
 }
 for i := 0; i < len(buf); i += 16 {
    addr := net.IP(buf[i:i+16])
    // do something with addr
 }

请注意,也可以使用 io.Reader 和 io.Writer 以增量方式读取和写入文件。此答案中的代码与应用程序一次性读取和写入文件的问题中的代码匹配。

【讨论】:

    【解决方案2】:

    由于您没有保留字节(如您所见,字节 0 出现在您的合法数据中),您有几个选择:

    • 如果您的所有值大小相同,或者可以设置为相同大小,请跳过分隔符,只计算每个值的适当字节数。
    • 当您以某种方式在数据中找到它时,保留一个字节并转义它 - 例如base64 对您的值进行编码并使用 0 字节作为分隔符(因为 0 不是有效的 base64 值)。
    • 为每个值加上一个字节(或某个固定数量的字节)作为前缀,以指示该值的长度。例如您可以使用单字节前缀处理 IPv4 和 IPv6 地址。

    第一个对于所有相同长度的值是最简单且最有效的。最后一个对于不同长度的值是最灵活和最有效的。

    【讨论】:

    • 最后需要顺序读取所有数据。对 OP 来说似乎不是问题,但这是一个限制。
    • 是的,虽然从技术上讲,bytes.Split 也是按顺序读取的。
    • 确实如此。但是使用前两个选项,可以跳转到流的任意部分,并开始读取并获取有效数据。第三种方法是不可能的。 (同样,对于 OP 来说显然不是问题)
    • 当然。这就是为什么我给出了选项而不是单一的解决方案 - 每个都有优点/缺点,必须在上下文中权衡。
    猜你喜欢
    • 1970-01-01
    • 2018-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-20
    • 2011-04-03
    • 2023-03-02
    • 1970-01-01
    相关资源
    最近更新 更多