Go map 知识点小结
使用 map 的注意事项
- map 的 key 的类型必须是可比较的,而 value 则可以是任意类型
- 可比较的类型:boolean,numeric,string,pointer,channel,interface,只包含上述类型的 struct 和 array
- 不可比较的类型:slice,map,function
- map 的类型是引用类型(reference type),这一点就像指针和切片。 (这句话有待商榷!)
- map 的零值是nil,对 nil map 可以进行读操作,但不能进行写操作(会 panic)
- 对 map 做读操作时,如果 key 不存在,返回的是 value 的零值。 注意:不会报错,这点和其他一些语言如 python 不同。
- delete(m, key) 函数删除map中的一个 entry:没有返回值;即使 key 不存在也不会报错
- map 不是并发安全的。如果需要并发读写 map,建议使用读写锁
sync.RWMutex
- 对 map 进行
for...range
迭代时,迭代的顺序是不定的
关于 map 的类型
maps 和 channels 都不是 reference variables。 更确切地说,Go 中就没有 reference variables —— 两个变量不可能共享相同的内存地址,但是两个变量的内容指向了相同的内存地址是有可能的。
理解 reference variable 可以看这样一个例子。
func main() {
var a int
var b, c = &a, &a
fmt.Println(b, c)
fmt.Println(&b, &c)
fmt.Println(*b, *c)
x := 10
b = &x
fmt.Println(b, c)
fmt.Println(&b, &c)
fmt.Println(*b, *c, a)
}
输出是:
0xc000100010 0xc000100010
0xc000102018 0xc000102020
0 0
0xc000100018 0xc000100010
0xc000102018 0xc000102020
10 0 0
b和c的内容储存了相同的内存地址(即a的地址),但是b和c本身的内存地址是不一样的。 当我们改变了b的内容时,它的值是变量x的内存地址。对b的赋值不会影响c和a的值。
对第二段程序稍作变化:
func main() {
var a int
var b, c = &a, &a
fmt.Println(b, c)
fmt.Println(&b, &c)
fmt.Println(*b, *c)
*b = 100
fmt.Println(b, c)
fmt.Println(&b, &c)
fmt.Println(*b, *c, a)
}
输出如下:
0xc00002c008 0xc00002c008
0xc00000e028 0xc00000e030
0 0
0xc00002c008 0xc00002c008
0xc00000e028 0xc00000e030
100 100 100
这里,代码对 b 的 dereference 赋值,也就是改变了a的值。这样一来,b,c,&b,&c的值都没变,但是b,c 和 a的值变成100了。
话说回来, map 到底是什么类型?
答案是:map 的值是指向 runtime.hmap
结构的指针(A map value is a pointer to a runtime.hmap
structure)
可以对照下面的例子加强理解:
func main() {
m := map[string]int{"originalMap": 1}
reallocate(m)
fmt.Println(m) // prints originalMap: 1
reallocatePtrWrong(&m)
fmt.Println(m) // prints originalMap: 1
reallocatePtr(&m)
fmt.Println(m) // prints overrideMap: 1
extend(m)
fmt.Println(m) // prints overrideMap: 1 extended: 2
}
func reallocate(m map[string]int) {
// Orginal value of m outside function not changed.
m = map[string]int{"overrideMap": 1}
}
func extend(m map[string]int) {
// Chaging m without realocation works, and don't need a pointer.
m["extended"] = 2
}
func reallocatePtrWrong(m *map[string]int) {
// Realocate using pointer don't become visible outisde if we
// only update the poniter value.
m = &map[string]int{"overrideMap": 1}
}
func reallocatePtr(m *map[string]int) {
// Realocate using pointer work if we do it right.
*m = map[string]int{"overrideMap": 1}
}
注意,上述的 reallocate
函数是日常写代码时很可能犯的错误,比如当想要清空一个 map 时,使用这类在函数内部赋值的操作,对外部是无效的。
参考
Go maps in action There is no pass-by-reference in Go
If a map isn’t a reference variable, what is it?
延伸阅读
Go 中 map 的实现细节: How the Go runtime implements maps efficiently (without generics)
Comments