2

我在 reflect.DeepEqual 中遇到了一些奇怪的行为。我有一个 type 的对象map[string][]string,其中一个键的值是一个空切片。当我使用 gob 对该对象进行编码,然后将其解码为另一个映射时,根据 reflect.DeepEqual,这两个映射不相等(即使内容相同)。

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 返回 true。

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

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

因此,DeepEqual 如何处理空切片并不是严格意义上的问题。这是和 gob 的序列化之间的一些奇怪的交互。

4

1 回答 1

2

这是因为您对一个空切片进行编码,并且在解码过程中,encoding/gob如果提供的切片(要解码的目标)不足以容纳编码值,则包仅分配一个切片。这记录在:gob:类型和值:

一般来说,如果需要分配,解码器会分配内存。如果不是,它将使用从流中读取的值更新目标变量。

由于编码了 0 个元素,并且nil切片完全能够容纳 0 个元素,因此不会分配切片。如果我们打印切片比较的结果,我们可以验证这一点nil

fmt.Println(m0["apple"] == nil, m2["apple"] == nil)

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

true false

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

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

请注意,非 nil 空切片和 nil 切片(例如,[]byte{} 和 []byte(nil))并不完全相等。

于 2018-06-06T15:42:03.033 回答