第五章(数据)[中]-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不会收缩内存,所以适当替换成新对象是必要的

posted @ 2023-01-09 23:36  巴达克  阅读(27)  评论(0编辑  收藏  举报