1、map的底层
底层数据结构:哈希表
2、map是否可比较
不可以,go中的map,slice,函数值都是不可比较的;
其中,map[Key]value中的key必须是可比较的,也就是可以通过 ==或!=操作符进行比较,value的值类型无所谓
3、删除一个不存在的key会panic吗?
不会,相当于delete()没生效
4、对不存在的key取值会发生什么?
对不存在key取值,得到类型零值
5、map中的value是可以被寻址的吗?
不可寻址,对于x=y的赋值操作必须要知道x的地址,所以
type Math struct{
x,y int
}
var m=map[string]Math{
"foo":{2,3},
}
func main(){
m["foo"].x = 5 //报错
}
6、当struct作为map的key时的一个坑
// 当struct作为map的key时,struct对象内部的某个字段发生改变后,查询map时就无法查找原来的值
m1 :=make(map[Math]string)
mm:=Math{1,2}
m1[mm]="haha"
fmt.Println(m1)
mm.x = 9
fmt.Println(m1[mm]) // 没有输出结果
7、map是有序还是无序的
无序,当遍历一个 map 对象的时候,迭代的元素的顺序是不确定的,无法保证两次遍历的顺序是一样的,也不能保证和插入的顺序一致。那怎么办呢?如果我们想要按照 key 的顺序获取 map 的值,需要先取出所有的 key 进行排序,然后按照这个排序的 key 依次获取对应的值
8、map[key]返回的结果可以是一个,也可以是两个值
func main() {
var m = make(map[string]int)
m["a"] = 0
fmt.Printf("a=%d; b=%d\n", m["a"], m["b"])
// a=0;b=0
av, aexisted := m["a"]
bv, bexisted := m["b"]
fmt.Printf("a=%d, existed: %t; b=%d, existed: %t\n", av, aexisted, bv, bexisted)
}
// a=0, existed: true; b=0, existed: false
9、只用var声明的map可以直接进行操作吗
不可以,和slice、mutex、RWmutex等struct类型不同,map对象必须在使用之前初始化,如果不初始化会出现panic异常
func main() {
var m map[int]int
m[100] = 100
}
10、map是并发安全的吗?
不是,go的内建的map对象不是线程(goroutine)安全的,并发读写的时候运行时会有检测,遇到并发问题就会导致panic。sync包的中sync.map
是并发安全的
func main() {
var m = make(map[int]int,10) // 初始化一个map
go func() {
for {
m[1] = 1 //设置key
}
}()
go func() {
for {
_ = m[2] //访问这个map
}
}()
select {}
}
改进:使用一个读写锁实现线程安全的map[int]int类型
type RWMap struct { // 一个读写锁保护的线程安全的map
sync.RWMutex // 读写锁保护下面的map字段
m map[int]int
}
// 新建一个RWMap
func NewRWMap(n int) *RWMap {
return &RWMap{
m: make(map[int]int, n),
}
}
func (m *RWMap) Get(k int) (int, bool) { //从map中读取一个值
m.RLock()
defer m.RUnlock()
v, existed := m.m[k] // 在锁的保护下从map中读取
return v, existed
}
func (m *RWMap) Set(k int, v int) { // 设置一个键值对
m.Lock() // 锁保护
defer m.Unlock()
m.m[k] = v
}
func (m *RWMap) Delete(k int) { //删除一个键
m.Lock() // 锁保护
defer m.Unlock()
delete(m.m, k)
}
func (m *RWMap) Len() int { // map的长度
m.RLock() // 锁保护
defer m.RUnlock()
return len(m.m)
}
func (m *RWMap) Each(f func(k, v int) bool) { // 遍历map
m.RLock() //遍历期间一直持有读锁
defer m.RUnlock()
for k, v := range m.m {
if !f(k, v) {
return
}
}
}
更高效的分片加锁并发maphttps://github.com/orcaman/concurrent-map
sync.map的实现
- 空间换时间,通过两个冗余的数据结构(只读的read字段,可写的dirty),来减少加锁对性能的影响,对只读字段(read)的操作不需要加锁
- 优先从read字段读取,更新,删除,因为对read字段的读取不需要加锁
- 动态调整:miss次数多了之后,将dirty数据提升为read,避免总时从dirty中加锁读取
- double-checking。加锁之后先还要检查read,确定真的不存在才操作dirty字段
- 延迟删除。删除一个键值只是打标记,只有在提升dirty字段为read字段的时候才清理删除的数据
- map的数据结构中包含的字段:mutex,read,dirty,misses
- sync.map拥有的方法:
- store方法:设置一个键值对
- load方法:读取一个key对应的值
- delete方法:删除。先从read操作开始,如果read中不存在,那么就从dirty中寻找,最终,如果存在就删除(将它的值标记为nil),如果项目不为nil或者没有被标记为expunged,那么还可以把它的值返回
- 其他方法:loadAndDelete,LoadOrStore,Rang,但是没有Len这样的查询map内部元素个数的方法。