第五章(数据)[中]-Map
字典(map)
-
字典要求key必须是支持相等运算符(
==、!=
)的数据类型,比如:数字、字符串、指针、数组、结构体、接口类型 -
字典是引用类型,创建字典有两种方式:a-make函数 b-初始化表达式
func main() {
//make函数方式创建map
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
fmt.Println(m) //map[a:1 b:2]
//初始化表达式语句创建,值为匿名数据结构类型
m2 := map[int]struct {
x int
}{
1: {x: 100},
2: {x: 200},
}
fmt.Println(m2) //map[1:{100} 2:{200}]
}
- map基本操作案例
func main() {
m := map[string]int{
"a": 1,
"b": 2,
}
m["a"] = 3 //修改
m["c"] = 4 //新增
if v, ok := m["d"]; ok {
println(v)
}
delete(m, "d") //删除键值对。不存在时,不会出错。
}
内建函数delete按照指定的键将元素从映射中删除。若m为nil或无此元素,delete不进行操作。
-
推荐使用ok-idiom模式(上面例子中),因为通过零值无法判断键值是否存在,或许存储的value本就是零
-
对字典进行迭代,每次返回的键值次序都不相同
func main() {
m := make(map[string]int)
for i := 0; i < 8; i++ {
m[string('a'+i)] = i
}
for i := 0; i < 4; i++ {
for k, v := range m {
fmt.Print(k, ":", v, " ")
}
println()
}
//map[a:0 b:1 c:2 d:3 e:4 f:5 g:6 h:7]
fmt.Println(m)
/*
g:6 h:7 a:0 b:1 c:2 d:3 e:4 f:5
d:3 e:4 f:5 g:6 h:7 a:0 b:1 c:2
e:4 f:5 g:6 h:7 a:0 b:1 c:2 d:3
g:6 h:7 a:0 b:1 c:2 d:3 e:4 f:5
*/
}
-
函数
len
返回当前键值对数量 -
cap
不接受字典类型内建函数cap返回 v 的容量,这取决于具体类型
数组:v中元素的数量,与 len(v) 相同 数组指针:*v中元素的数量,与len(v) 相同 切片:切片的容量(底层数组的长度);若 v为nil,cap(v) 即为零 信道:按照元素的单元,相应信道缓存的容量;若v为nil,cap(v)即为零
-
因内存访问安全和哈希算法缘故,map被设计成不可寻址(not addressable),故不能直接修改value成员(结构或数组)
func main() {
type data struct {
name string
age byte
}
m := map[string]data{
"info": {"wang", 8},
}
fmt.Println(m["info"].age) //8
fmt.Printf("address:%p", &m["info"]) //cannot take the address of m["info"]
m["info"].age = 9 //cannot assign to struct field m["info"].age in map
}
正确做法,先取值并赋值给变量后再修改
func main() {
type data struct {
name string
age byte
}
m := map[string]data{
"info": {"wang", 8},
}
//先取值
u := m["info"]
//修改值
u.age = 9
//再赋值
m["info"] = u
fmt.Println(m)
}
- 当值为指针时就可以修改
func main() {
type data struct {
name string
age byte
}
//当值为指针时就可以修改
m := map[string]*data{
"info": {"wang", 8},
}
m["info"].age += 1
fmt.Println(m["info"].age) //9
}
- 不能对nil字典(只声明未初始化的map)进行写操作,但可以读
func main() {
var m map[string]int
println(m["a"]) //0
m["a"] = 1 //panic: assignment to entry in nil map
}
- 内容为空的字段与nil不同,初始化的map等同于make操作
func main() {
var m1 map[string]int
m2 := map[string]int{} //已初始化,等同make操作
m2["a"] = 666
fmt.Println(m1 == nil, m2 == nil, m2["a"]) //true, false,666
}
- 运行时会对map并发操作做出检测。如果某个任务正在对字典进行写操作,那么其它任务就不能对该map执行并发操作(读、写、删除),否则会导致进程崩溃。
func main() {
m := make(map[string]int)
go func() {
for {
m["a"] += 1 //写操作
time.Sleep(time.Microsecond)
//fmt.Println("write-m[a]:", m["a"])
}
}()
go func() {
for {
_ = m["b"] //读操作
time.Sleep(time.Microsecond)
}
}()
select {} //阻止进程退出
}
输出
fatal error: concurrent map read and map write
- 可用
sync.RWMutex
实现同步,避免读写操作同时进行
func main() {
var lock sync.RWMutex //声明读写锁
m := make(map[string]int)
go func() {
for {
lock.Lock() //写锁
m["a"] += 1 //写操作
lock.Unlock()
}
}()
go func() {
for {
lock.RLock() //读锁
_ = m["b"] //读操作
lock.RUnlock()
//time.Sleep(time.Microsecond)
}
}()
select {} //阻止进程退出
}
- map本身就是指针包装,传参时无需再次取地址
func main() {
m := make(map[string]int64)
m["a"] = 1
m["c"] = 3
test(m)
fmt.Printf("m: %p, %d,%d,%T\n", m, unsafe.Sizeof(m), unsafe.Sizeof(m["a"]), m["a"]) //m: 0xc000090480,8,8,int64
fmt.Println(m) //map[a:1 b:2 c:3]
}
func test(x map[string]int64) {
x["b"] = 2
fmt.Printf("x: %p\n", x) //x: 0xc000090480
}
-
在创建map时预先准备足够的空间有助于提升性能,减少扩张时的内存分配和重新哈希操作
-
map不会收缩内存,所以适当替换成新对象是必要的