将地图序列化为 gob 后 DeepEqual 不正确

DeepEqual incorrect after serializing map into gob

我在使用 reflect.DeepEqual 时遇到了一些奇怪的行为。我有一个 map[string][]string 类型的对象,其中一个键的值为空切片。当我用gob编码这个对象,然后解码成另一个map时,根据reflect.DeepEqual这两个map是不相等的(尽管内容是一样的)

package main

import (
    "fmt"
    "bytes"
    "encoding/gob"
    "reflect"
)

func main() {
    m0 := make(map[string][]string)
    m0["apple"] = []string{}

    // Encode m0 to bytes
    var network bytes.Buffer
    enc := gob.NewEncoder(&network)
    enc.Encode(m0)

    // Decode bytes into a new map m2
    dec := gob.NewDecoder(&network)
    m2 := make(map[string][]string)
    dec.Decode(&m2)

    fmt.Printf("%t\n", reflect.DeepEqual(m0, m2)) // false
    fmt.Printf("m0: %+v != m2: %+v\n", m0, m2) // they look equal to me!
}

输出:

false
m0: map[apple:[]] != m2: map[apple:[]]

来自后续实验的一些笔记:

如果我将 m0["apple"] 的值设为非空切片,例如 m0["apple"] = []string{"pear"},则 DeepEqual returns 为真。

如果我将该值保留为空切片,但我从头开始构建相同的映射而不是使用 gob,则 DeepEqual returns true:

m1 := make(map[string][]string)
m1["apple"] = []string{}
fmt.Printf("%t\n", reflect.DeepEqual(m0, m1)) // true!

所以 DeepEqual 如何处理空切片并不是严格意义上的问题;它和 gob 的序列化之间有些奇怪的相互作用。

这是因为你编码了一个空切片,并且在解码过程中 encoding/gob package only allocates a slice if the one provided (the target to decode into) is not big enough to accomodate the encoded values. This is documented at: gob: Types and Values:

In general, if allocation is required, the decoder will allocate memory. If not, it will update the destination variables with values read from the stream.

由于编码了 0 个元素,并且 nil 切片完全能够容纳 0 个元素,因此不会分配任何切片。如果我们打印切片与 nil:

的比较结果,我们可以验证这一点
fmt.Println(m0["apple"] == nil, m2["apple"] == nil)

上面的输出是(在Go Playground上试试):

true false

请注意,fmt 包以相同的方式打印 nil 切片值和空切片:与 [] 一样,您不能依赖其输出来判断切片是否为 nil与否。

并且 reflect.DeepEqual()nil 切片和一个空但非 nil 切片不同(非深度相等):

Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) are not deeply equal.