map介绍
map类似于其他的语言的字典。用于存放一个key,value值对。定义的语法map[type of key][type of value]。
map的定义及初始化
通过向 make 函数传入键和值的类型,可以创建 map。make(map[type of key]type of value) 是创建 map 的语法。make是专门用来创建初始化map,slice,chan的函数。
因为var或new在声明map之后,是没有对map再进行内存空间初始化。此时的map是一个零值,nil。相当于是map是一个指针类型值,而没有对这个指针值做初始化。
package main
import (
"fmt"
)
func main() {
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
}
map的属性
- map是引用类型。
- map的底层数据结构
- map删除元素,不会释放内存
map的底层数据结构
map的底层结构是hmap(即hashmap的缩写),核心元素是一个由若干个桶(bucket,结构为bmap)组成的数组,每个bucket可以存放若干元素(通常是8个),key通过哈希算法被归入不同的bucket中。当超过8个元素需要存入某个bucket时,hmap会使用extra中的overflow来拓展该bucket
map的操作
- map添加获取元素(判断key值是否存在)
- 删除map元素
- 遍历map
- 获取map长度
- map的判等操作
- 线程安全map
添加&获取元素
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000 //增加元素值
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee]) //获取元素值
value,ok := personSalary['nothing] //获取元素值,做判等操作。 ok 是 true,表示 key 存在,key 对应的值就是 value ,反之表示 key 不存在
}
删除map元素
删除 map 中 key 的语法是 delete(map, key)。这个函数没有返回值。
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("map before deletion", personSalary)
delete(personSalary, "steve")
fmt.Println("map after deletion", personSalary)
}
遍历map
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("All items of a map")
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
}
输出:
All items of a map
personSalary[mike] = 9000
personSalary[steve] = 12000
personSalary[jamie] = 15000
获取map长度
package main
import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("length is", len(personSalary))
}
输出:length is 3。
map的判等操作
因为map是一个指针类型。map当中可能存储不可比较的数据类型(如指针类型)。没办法通过==直接进行判等操作。
针对此类型的数据的判等操作可以使用reflect.DeepEqual(v1,v2)来进行判断。此时必须内部数据完全相等。包括指针指向的最终的数据的数值。
查看以下示例:
package main
import (
"fmt"
"reflect"
)
func main() {
a := make(map[string]string)
a["one"] = "name1"
a["two"] = "name2"
a["three"] = "name3"
b := map[string]string{
"one": "name1",
"two": "name2",
"three": "name3",
}
if reflect.DeepEqual(a,b){
fmt.Println("equal")
}else{
fmt.Println("no equal")
}
b["three"] = "name4"
if reflect.DeepEqual(a,b){
fmt.Println("change: equal")
}else{
fmt.Println("change: no equal")
}
}
输出:
equal
change: no equal
非线程安全map
Go 1.6之前, 内置的map类型是部分goroutine安全的,并发的读没有问题,并发的写可能有问题。自go 1.6之后, 并发地读写map会报错,
,下列程序中一个goroutine一直读,一个goroutine一只写同一个键值,即即使读写的键不相同,而且map也没有"扩容"等操作,代码还是会报错。
package main
func main() {
m := make(map[int]int)
go func() {
for {
_ = m[1]
}
}()
go func() {
for {
m[2] = 2
}
}()
select {}
}
输出错误:
fatal error: concurrent map read and map write。
如何解决并发安全问题
但是,很多时候,我们会并发地使用map对象,尤其是在一定规模的项目中,map总会保存goroutine共享的数据。在Go官方blog的Go maps in action一文中,提供了一种简便的解决方案。
var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
它使用嵌入struct为map增加一个读写锁。
读数据的时候很方便的加锁:
counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
写数据的时候:
counter.Lock()
counter.m["some_key"]++
counter.Unlock()
以上的解决方案相当简洁,并且利用读写锁而不是Mutex可以进一步减少读写的时候因为锁带来的性能。在map的数据非常大的情况下,一把锁会导致大并发的客户端共争一把锁。
1.9使用sync.Map并发安全Map
操作方法
- Load
- Store
- Delete
- Range
详细的源码解读参考:https://colobu.com/2017/07/11/dive-into-sync-Map/
package main
import (
"fmt"
"sync"
)
func main() {
//生命初始化一个安全map
var m sync.Map
//安全的操作存储值
m.Store(1,"a")
m.Store(2,"b")
//LoadOrStore
//若key不存在,则存入key和value,返回false和输入的value
v,ok := m.LoadOrStore("1","aaa")
fmt.Println(ok,v) //false aaa
//若key已存在,则返回true和key对应的value,不会修改原来的value
v,ok = m.LoadOrStore(1,"aaa")
fmt.Println(ok,v) //false aaa
//Load 获取元素值
v,ok = m.Load(1)
if ok{
fmt.Println("it's an existing key,value is ",v)
} else {
fmt.Println("it's an unknown key")
}
//Range
//遍历sync.Map, 要求输入一个func作为参数
f := func(k, v interface{}) bool {
//这个函数的入参、出参的类型都已经固定,不能修改
//可以在函数体内编写自己的代码,调用map中的k,v
fmt.Println(k,v)
return true
}
m.Range(f)
//Delete
m.Delete(1)
fmt.Println(m.Load(1))
}
小结:
- map是存储一个key-value键值对。key值必须是值类型的。
- map的定义方法: make(map[string]int); m := map[string]string{"a":"a","b":"b"}
- map是引用类型值,底层是一个hash桶
- map非并发安全
- 并发的安全sync.Map。var m sync.Map。 m.Store("key","value"),m.Load("key"),m.Delete("key"),m.Range(func(k,v interface{})bool{ //range需要传入一个函数,在函数内部加入处理逻辑})
参考连接:
https://colobu.com/2017/07/11/dive-into-sync-Map/