文章目录
什么是map
map这个结构在很多编程语言内都有,包括我们今天的主角go语言
。今天我们将几个方面为大家分析Go中的Map。
其中这个有key与value组成的结构体我们称为bucket
,会在下文在做介绍。
map的使用
// 直接声明map并分配内存空间
var m = map[string]interface{}{"name": "leslie", "age": 18}
// 声明map
var m map[string]string
// 为map分配内存空间并初始化空值,第二个参数为长度,也可以不传
m = make(map[string]string, 8)
// 直接对map赋值
m["name"] = "leslie"
m["age"] = "18"
// 循环遍历map中每一个key&value对
for key, value := range m {
fmt.Println(key, value)
}
// 对map取值,可以有两个参数返回,第一个返回值为map内对应值,第二个返回值代表是否存在,如果ok为false,则value为默认空值
value, ok := m["csdn"]
if ok {
fmt.Println(value)
}
// 删掉map中key为csdn的value
delete(m, "csdn")
// len(m)可以获取到当前map的元素长度
fmt.Println(len(m))
以上是map比较常用的几个操作,掌握起来还是比较简单的。
底层结构
刚刚我们提到map底层是一个数组,数组下标是通过key值进行hash运算再去模这个数组长度得到的。而数组内存着是一个我们称为bucket
,或者说是bmap
的结构体。
map的源码位于 src/runtime/map.go中:
//bucket结构体定义 b就是bucket
type bmap{
// tophash generally contains the top byte of the hash value
// for each key in this bucket. If tophash[0] < minTopHash,
// tophash[0] is a bucket evacuation state instead.
tophash [bucketCnt]uint8
// Followed by bucketCnt keys and then bucketCnt values.
// NOTE: packing all the keys together and then all the values together makes the // code a bit more complicated than alternating key/value/key/value/... but it allows // us to eliminate padding which would be needed for, e.g., map[int64]int8.// Followed by an overflow pointer.
}
这里英文注释用翻译软件翻译出来有点怪,所以就不翻译了,这里提取几个重要的信息:
以下用图简单表示map的结构:
当往map中存储一个kv对时,通过k获取hash值,hash值的低八位和bucket数组长度取余,定位到在数组中的那个下标,hash值的高八位存储在bucket中的tophash中,用来快速判断key是否存在,key和value的具体值则通过指针运算存储,当一个bucket满时,通过overfolw指针链接到下一个bucket。
这里观察图片,肯定会有一个疑问不是每个数组元素对应一个kv对吗?为什么bucket里面会有这么多kv?为什么bucket里还会有overfolw这个属性,为什么会不够装呢?别着急,下面介绍。
hash冲突时解决方法
我们知道hash计算会将一个输入转换成一定长度的字符串,但是难保不齐会出现几个输入经过hash计算后会出现相同字符串的情况,这种情况称为hash碰撞
。
而在map中,解决hash冲突一般有两种方式。
- 线性探测法
就是按照顺序来,从冲突的下标处开始往后探测,到达数组末尾时,从数组开始处探测,直到找到一个空位置存储这个key,当数组都找不到的情况下回扩容。这个就是用数组的思想。 - 拉链法
简单理解为链表,当key的hash冲突时,我们在冲突位置的元素上形成一个链表,通过指针互连接,当查找时,发现key冲突,顺着链表一直往下找,直到链表的尾节点,找不到则返回空。就是我们上图的实现。简单来说,hash冲突后,就和之前的key放在同一个bucket
内,溢出的部分通过上文提到overflow
相连。
两者优缺点
- 由上面可以看出拉链法比线性探测处理简单
- 线性探测查找是会被拉链法会更消耗时间
- 线性探测会更加容易导致扩容,而拉链不会
- 拉链存储了指针,所以空间上会比线性探测占用多一点
- 拉链是动态申请存储空间的,所以更适合链长不确定的