Go | fmt:按键序打印 map

go 1.12 版本之前,打印 map 得到的结果是不确定的。也就是说,对于同一个 map,每次打印得到的 key 顺序都有可能不一样。

于是,go 1.12 做了一个改动:以 key 的顺序打印 map。也就是说,针对 map,fmt.printValue 会先调用 fmtsort.Sort 方法(位于 src/internal/fmtsort)获取已排序的 key 列表,然后依次打印对应的 value。

下面我们来看看比较规则:

  • 在适用情况下,nil 的值最小
  • 对于所有比较,最开始会比较 key 的类型。如果类型不相同,则返回 a < b(假设 a 是比较函数的第一个参数,b 是第二个参数)
  • 整型、浮点数和字符串:由运算符 < 确定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 整型
func printIntMap(){
m := map[int]int{3: 4, 1: 5, 0: 6, 55: 7}
fmt.Println(m) // map[0:6 1:5 3:4 55:7]
}
// 浮点数
func printFloatMap(){
m := map[float32]int{1.2: 3, 0.7: 4, 3.5: 5}
fmt.Println(m) // map[0.7:4 1.2:3 3.5:5]
var z float32
m[z/z] = 6
fmt.Println(m) // map[NaN:6 0.7:4 1.2:3 3.5:5]
}
// 字符串
func printStringMap(){
m := map[string]int{"abc": 0, "cdc": 1, "aab": 2}
fmt.Println(m) // map[aab:2 abc:0 cdc:1]
}

注意:NaN < 非 NaN 浮点数小

  • 布尔型:true > false
1
2
m := map[bool]int{true: 2, false: 3}
fmt.Println(m) // map[false:3 true:2]
  • complex:先比较 real 部分,相等则比较 imag 部分
1
2
3
4
5
func printComplexMap() {
m := map[complex64]int{complex(1,2): 0, complext(-1, 3): 1, complex(4, 1): 2}
fmt.Println(m)
// map[(-1+3i):1 (1+2i):0 (4+1i):2]
}
  • 指针:使用机器地址进行比较

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // from src/internal/fmtsort/sort_test.go
    func pointerMap() map[*int]string {
    m := make(map[*int]string)
    var ints [3]int
    for i := 2; i >= 0; i-- {
    m[&ints[i]] = fmt.Sprint(i)
    }
    return m
    }

    func printPointerMap() {
    m := pointerMap()
    fmt.Println(m)
    // map[0xc000014180:0 0xc000014188:1 0xc000014190:2]
    m[nil] = "3"
    fmt.Println(m)
    // map[<nil>:3 0xc000014180:0 0xc000014188:1 0xc000014190:2]
    // 不同机器运行结果不同。但是都是按照指针地址进行排序的,并且 nil 最小
    }
  • channel:使用机器地址进行比较

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // from src/internal/fmtsort/sort_test.go
    func chanMap() map[chan int]string {
    m := make(map[chan int]string)
    var chans = [3]chan int{make(chan int), make(chan int), make(chan int)}
    for i := 2; i >= 0; i-- {
    m[chans[i]] = fmt.Sprint(i)
    }
    return m
    }

    func printChanMap() {
    m := chanMap()
    fmt.Println(m)
    // map[0xc000082060:0 0xc0000820c0:1 0xc000082120:2]
    m[nil] = "3"
    fmt.Println(m)
    // map[<nil>:3 0xc000082060:0 0xc0000820c0:1 0xc000082120:2]
    // 不同机器运行结果不同。但是都是按照指针地址进行排序的,并且 nil 最小
    }
  • struct:依次比较每个字段。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // from src/internal/fmtsort/sort_test.go
    type toy struct {
    a int // Exported.
    C string // Exported
    b int // Unexported.
    }

    func printStructMap() {
    m := map[toy]string{
    toy{7, "ab", 2}: "7ab2",
    toy{7, "bc", 1}: "7bc1",
    toy{3, "ac", 4}: "3ac4",
    }
    fmt.Println(m)
    // map[{3 ac 4}:3ac4 {7 ab 2}:7ab2 {7 bc 1}:7bc1]
    // 先比较字段 a。不相等则直接返回比较结果
    // 相等则继续比较字段 C。不相等则直接返回比较结果
    // 以此类推
    }
  • array:依次比较每个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func printArrayMap() {
    m := map[[2]int]string{
    [2]int{1, 1}: "0",
    [2]int{0, 1}: "1",
    [2]int{1, 2}: "2",
    [2]int{2, 0}: "3",
    }
    fmt.Println(m)
    // map[[0 1]:1 [1 1]:0 [1 2]:2 [2 0]:3]
    // 先比较列表中的第一个元素。
    // 如果不相等,则比较第二个元素。
    // 以此类推,直到得出比较结果
    }

说明:由于 slice 不能作为 map 的key。因此不做考虑

  • interface:首先比较描述具体类型的 reflect.Type,再比较值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    func printInterfaceMap() {
    var interf interface{}
    m := map[interface{}]string{
    0: "0",
    true: "1",
    -1: "2",
    interf: "3",
    "abc": "4",
    1.5: "5",
    "aa": "6",
    }

    fmt.Println(m)
    // map[<nil>:3 true:1 aa:6 1.5:5 abc:4 -1:2 0:0]
    // 输出结果每次运行各不相同。
    // 但是,nil 永远是最小的。
    // 同类型的 key 根据比较规则输出。
    // 不同类型的 key 根据检验顺序随机输出。
    }

interface 类型的 key 比较是先比较 key 的类型的。在 golang 中,这种情况下 key 的类型总是为 reflect.Ptr,而因为 reflect.Ptr 是个常量,故而对该类型做比较结果总是相等。因此,在类型比较这一步一般是不能得到结果,必须进行 key 的值比较。

在进行 key 的值比较时,因为会先对值类型进行比较。此时,不同类型的 key 根据入参顺序就可以得出比较结果。而同种类型的 key 则按照以上规则得出比较结果。

对于 map 打印,由于增加了排序操作,因此不可避免地会对性能有一定的冲击。这对于高性能要求的程序而言,是必须进行考虑的。

参考