这是我参与「第五届青训营 」伴学笔记创作活动的第5天
map
map表的底层原理是哈希表,其结构体定义如下:
type Map struct {
Key *Type // Key type
Elem *Type // Val (elem) type
Bucket *Type // 哈希桶
Hmap *Type // 底层使用的哈希表元信息
Hiter *Type // 用于遍历哈希表的迭代器
}
其中的Hmap 的具体化数据结构如下:
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // map目前的元素数目
flags uint8 // map状态(正在被遍历/正在被写入)
B uint8 // 哈希桶数目以2为底的对数(哈希桶的数目都是 2 的整数次幂,用位运算来计算取余运算的值, 即 N mod M = N & (M-1)))
noverflow uint16 //溢出桶的数目, 这个数值不是恒定精确的, 当其 B>=16 时为近似值
hash0 uint32 // 随机哈希种子
buckets unsafe.Pointer // 指向当前哈希桶的指针
oldbuckets unsafe.Pointer // 扩容时指向旧桶的指针
nevacuate uintptr // 桶进行调整时指示的搬迁进度
extra *mapextra // 表征溢出桶的变量
}
以上Hmap基本都是涉及到了哈希桶和溢出桶,我们首先看一下它的数据结构,如下:
type bmap struct {
topbits [8]uint8 // 键哈希值的高8位
keys [8]keytype // 哈希桶中所有键
elems [8]elemtype // 哈希桶中所有值
//pad uintptr(新的 go 版本已经移除了该字段, 我未具体了解此处的 change detail, 之前设置该字段是为了在 nacl/amd64p32 上的内存对齐)
overflow uintptr
}
我们会发现哈希桶bmap一般指定其能保存8个键值对,如果多于8个键值对,就会申请新的buckets,并将其于之前的buckets链接在一起。
在具体插入时,首先会根据key值采用相应的hash算法计算对应的哈希值,将哈希值的低8位作为Hmap结构体中buckets数组的索引,找到key值所对应的bucket,将哈希值的高8位催出在bucket的tophash中。
特点如下:
- map是无序的(原因为无序写入以及扩容导致的元素顺序发生变化),每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
- map的长度是不固定的,也就是和slice一样,也是一种引用类型
- 内置的len函数同样适用于map,返回map拥有的key的数量
- map的key可以是所有可比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型……也可以键。
如下方式即可进行初始化:
var a map[keytype]valuetype
//a map表名字
//keytype 键类型
//valuetype 键对应的值的类型
//除此以外还可以使用make进行初始化,代码如下:
var m map[string]int = map[string]int{"hunter":12,"tony":10}
//我们还可以使用初始值进行初始化,如下:
var m map[string]int = map[string]int{"hunter":12,"tony":10}
插入数据
map的数据插入代码如下:
map_variable["mars"] = 27
插入过程如下:
- 根据key值计算出哈希值
- 取哈希值低位和hmap.B取模确定bucket位置
- 查找该key是否已经存在,如果存在则直接更新值
- 如果没有找到key,则将这一对key-value插入
删除数据
delete(map, key) 函数用于删除集合的元素, 参数为 map 和其对应的 key。删除函数不返回任何值。相关代码如下:
countryCapitalMap := map[string] string {"France":"Paris","Italy":"Rome","Japan":"Tokyo","India":"New Delhi"}
/* 删除元素 */
delete(countryCapitalMap,"France");
查找数据
通过key获取map中对应的value值。语法为:map[key] .但是当key如果不存在的时候,我们会得到该value值类型的默认值,比如string类型得到空字符串,int类型得到0。但是程序不会报错。
所以我们可以使用ok-idiom获取值,如下:value, ok := map[key] ,其中的value是返回值,ok是一个bool值,可知道key/value是否存在。
在map表中的查找过程如下:
- 查找或者操作map时,首先key经过hash函数生成hash值
- 通过哈希值的低8位来判断当前数据属于哪个桶
- 找到桶之后,通过哈希值的高八位与bucket存储的高位哈希值循环比对
- 如果相同就比较刚才找到的底层数组的key值,如果key相同,取出value
- 如果高八位hash值在此bucket没有,或者有,但是key不相同,就去链表中下一个溢出bucket中查找,直到找到链表的末尾
- 如果查找不到,也不会返回空值,而是返回相应类型的0值。