从 map[string][]string 返回的空列表有什么特别之处

What's special about an empty list returned from a map[string][]string

这是一个奇怪的情况,我不明白 Go 在做什么。我以一些代码结束,这些代码使用了尚未插入该键的地图默认值。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    mmm := map[string][]string{}
    nnn := map[string][]string {
        "a": {},
    }
    
    x := mmm["a"]
    y := nnn["a"]
    z := []string{}
    
    fmt.Println(reflect.DeepEqual(x,y))
    fmt.Println(reflect.DeepEqual(x,z))
    fmt.Println(reflect.DeepEqual(y,z))

    fmt.Printf("%T, %T, %T", x, y, z)
}

我得到了意外的输出

false
false
true
[]string, []string, []string

我期望 true 全面。执行此操作的地图的默认值是什么?

如果再添加打印行:

fmt.Printf("%#v, %#v, %#v", x, y, z)

一切都会明朗的。它输出(在 Go Playground 上尝试):

[]string(nil), []string{}, []string{}

xnil 的切片,而 y 是具有 0 长度的非 nil 切片,就像 z.

使用不在地图中的键索引地图,会导致地图值类型为 zero value。在你的例子中,值类型是 []string,它是一个切片类型,切片类型的零值是 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.

首先,快速更正一下,这种数据类型称为“切片”,而不是“列表”。

当您查找不存在的地图键时,您会得到地图值类型的“零值”。在切片的情况下,零值是 nil 切片。 nil 切片的行为通常很像空切片(例如,nil 切片的 len 为零),但它们不被认为是相同的。在 Go 中实际上并没有为切片定义相等性,因此由 reflect.DeepEqual 决定这里的相等性意味着什么。

您将在解释此处行为的文档中看到这条注释:

Slice values are deeply equal when all of the following are true: they are both nil or both non-nil, they have the same length, and either they point to the same initial entry of the same underlying array (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) are not deeply equal.