哈希表用于存储键值对的映射关系,具有O(1)的读写性能。通过哈希函数可以将不同的键映射到不同索引上,当不同的键映射到同一个索引上时,会产生哈希冲突,可通过开放寻址法、链表法来解决哈希冲突,其中Go使用的是链表法。
一、数据结构
map将键值对存放在桶数组中,每个桶只保存8个键值对,通过键的低8位选择桶,通过键的高8位选择放在桶的哪个位置。如果有超过8个键值对映射到同一个桶,则会放到溢出桶
type hmap struct {
count int // 当前map的元素数量
flags uint8
B uint8 // 当前持有的buckets数量,因为总是2的倍数,所以存的是对数值
noverflow uint16 // 溢出的bucket数量
hash0 uint32 // hash seed
buckets unsafe.Pointer // 指向bucket的指针
oldbuckets unsafe.Pointer // 用于扩容时保存之前的bucket,大小是当前bucket的一半
nevacuate uintptr // 迁移进度
extra *mapextra // 当键值非指针时使用
}
type mapextra struct {
// 当键值非指针时,溢出桶存放到mapextra结构中,overflow存放buckets中的溢出桶,oldoverflow存放oldbuckets中的溢出桶,nextOverflow预分配溢出桶空间
overflow *[]*bmap
oldoverflow *[]*bmap
nextOverflow *bmap
}
type bmap struct {
tophash [bucketCnt]uint8 // bucketCnt=8,一个桶只能放8个键值对,tophash存放了键的哈希的高8位,通过比较不同键的哈希的高 8 位可以减少访问键值对次数以提高性能
}
二、操作
1.创建map
map是通过makemap方法创建的,该方法接受一个要创建的map的类型,要创建的map的大小,以及一个hmap指针,返回一个hmap指针
makemap(t *maptype, hint int, h *hmap) *hmap
首先对要创建的map大小是否溢出进行检查
mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
hint = 0
}
若传入的h为空则初始化一个map
if h == nil {
h = new(hmap)
}
h.hash0 = fastrand()
然后计算合适的桶的数量
B := uint8(0)
for overLoadFactor(hint, B) {
B++
}
h.B = B
然后对桶进行初始化,其中如果B为0,则桶的初始化延迟到赋值时进行
if h.B != 0 {
var nextOverflow *bmap
h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
if nextOverflow != nil {
h.extra = new(mapextra)
h.extra.nextOverflow = nextOverflow
}
}
return h
其中调用了makeBucketArray计算出要创建的桶的数量并为此分配内存
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
base := bucketShift(b)
nbuckets := base
// 当桶的数量小于2的4次方时,由于数据量较小,使用溢出桶可能性较低,省去创建过程,否则还需额外创建2的b-4次方个桶
if b >= 4 {
nbuckets += bucketShift(b - 4)
sz := t.bucket.size * nbuckets
up := roundupsize(sz)
if up != sz {
nbuckets = up
}
}
if dirtyalloc == nil {
// 分配一个新的数组
buckets = newarray(t.bucket, int(nbuckets))
} else {
// 清理原来的数组
buckets = dirtyalloc
size := t.bucket.size * nbuckets
if t.bucket.ptrdata != 0 {
memclrHasPointers(buckets, size)
} else {
memclrNoHeapPointers(buckets, size)
}
}
if base != nbuckets {
// 找到溢出桶的位置,因为预先分配的溢出桶的overflow都是nil,若要知道哪个溢出桶是最后一个需要进行标记
nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))
last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))
last.setoverflow(t, (*bmap)(buckets))
}
return buckets, nextOverflow
}
2.获取元素
当使用m[key]获取对应的value时,会调用mapaccess1方法,该方法接受一个map的类型,一个hmap的指针,以及key的指针。该方法永远不会返回nil,若该key不存在map时,会返回值的类型对应的空值,因此还可通过返回一个bool值标识该key是否存在mapÿ