map原理
-
map是由key-value对组成的,key只会出现一次
-
它的任务是设计一种数据结构用来维护一个集合的数据,并且可以同时对集合进行增删查改的操作。最主要的数据结构有两种:
哈希查找表(Hash table)、搜索树(Search tree)。 -
哈希函数---散列函数:是可以用于将任意大小的数据映射到固定大小值的函数,常见的包括MD5、SHA系列等。
- 哈希冲突:当输入数据足够大,大到能超过固定大小值的组合能表达的最大数量数,冲突将不可避免!
- 哈希查找表用一个哈希函数将 key 分配到不同的桶(bucket,也就是数组的不同 index)。
- 哈希查找表一般会存在“碰撞”的问题,就是说不同的 key 被哈希到了同一个 bucket。解决哈希冲突一般有两种应对方法:
链表法和开放地址法。链表法将一个 bucket 实现成一个链表,落在同一个 bucket 中的 key 都会插入这个链表。开放地址法则是碰撞发生后,通过一定的规律,在数组的后面挑选“空位”,用来放置新的 key
-
搜索树法一般采用自平衡搜索树,包括:AVL 树,红黑树。
-
Go 语言采用的是哈希查找表,并且使用链表解决哈希冲突。
源码:
type hmap struct {
count int // 代表哈希表中的元素个数,调用len(map)时,返回的就是该字段值。
flags uint8 // 状态标志,下文常量中会解释四种状态位含义。
B uint8 // buckets(桶)的对数log_2(哈希表元素数量最大可达到装载因子*2^B)
noverflow uint16 // 溢出桶的大概数量。
hash0 uint32 // 哈希种子。
buckets unsafe.Pointer // 指向buckets数组的指针,数组大小为2^B,如果元素个数为0,它为nil。
oldbuckets unsafe.Pointer // 如果发生扩容,oldbuckets是指向老的buckets数组的指针,老的buckets数组大小是新的buckets的1/2。非扩容状态下,它为nil。
nevacuate uintptr // 表示扩容进度,小于此地址的buckets代表已搬迁完成。
extra *mapextra // 这个字段是为了优化GC扫描而设计的。当key和value均不包含指针,并且都可以inline时使用。extra是指向mapextra类型的指针。
- 每个桶只能存八个key-value,如果超了就新建一个桶,通过
overflow指针连接起来。
创建map
- 调用makemap函数,初始化hmap结构体,返回*hmap指针
哈希函数
key定位
key 经过哈希计算后得到哈希值,共 64 个 bit 位。计算它到底要落在哪个桶时,只会用到最后 B 个 bit 位。如果 B = 5,那么桶的数量,也就是 buckets 数组的长度是 2^5 = 32。