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 | // 整型 |
注意:NaN < 非 NaN 浮点数小
- 布尔型:true > false
1 | m := map[bool]int{true: 2, false: 3} |
- complex:先比较 real 部分,相等则比较 imag 部分
1 | func printComplexMap() { |
指针:使用机器地址进行比较
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:依次比较每个元素
> 说明:由于 slice 不能作为 map 的key。因此不做考虑1
2
3
4
5
6
7
8
9
10
11
12
13func 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]
// 先比较列表中的第一个元素。
// 如果不相等,则比较第二个元素。
// 以此类推,直到得出比较结果
}interface:首先比较描述具体类型的 reflect.Type,再比较值
> interface 类型的 key 比较是先比较 key 的类型的。在 golang 中,这种情况下 key 的类型总是为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func 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 根据检验顺序随机输出。
}reflect.Ptr
,而因为reflect.Ptr
是个常量,故而对该类型做比较结果总是相等。因此,在类型比较这一步一般是不能得到结果,必须进行 key 的值比较。 > > 在进行 key 的值比较时,因为会先对值类型进行比较。此时,不同类型的 key 根据入参顺序就可以得出比较结果。而同种类型的 key 则按照以上规则得出比较结果。
对于 map 打印,由于增加了排序操作,因此不可避免地会对性能有一定的冲击。这对于高性能要求的程序而言,是必须进行考虑的。
参考
- https://github.com/golang/go/issues/21095
- https://go-review.googlesource.com/c/go/+/142737/