文章目录
1. hashmap 的基本方案
通常在学习hashmap 数据结构,会遇到两种方法实现,分别是开放寻址法和拉链法。下面介绍一个这两种方法的区别。
1.1 开放寻址法
- 假设在map中已经存在三个节点,现在插入第四个节点信息,初始状态如下:
- 先通过hash函数计算,然后对其进行取模(根据map 大小长度),如果计算出来的位置已经有其他信息占据,则会往后移动直到找到空位置。
1.2 拉链法
- 假设hashmap中已经存在一个节点,现在继续往里面加入一个,初始信息如下:
- 通过hashi计算,如果计算出来的位置上已经存在其他信息,则是通过链表的形式往下加入
拉链法的几个专有名词
2. go中的map
2.1 源码位置
在runtime/map.go
中,如图:
2.2 数据结构示意图
2.3 bucket 数据结构
3. map 的初始化
3.1 通过make 初始化
- code
func main() {
m := make(map[string]int, 10)
fmt.Println(m)
}
- 通过命令,汇编分析
go build -gcflags -S main.go
- 汇编代码
main.go:6) LEAQ type.map[string]int(SB), AX
main.go:6) MOVQ AX, (SP)
main.go:6) MOVQ $10, 8(SP)
main.go:6) MOVQ $0, 16(SP)
main.go:6) PCDATA $1, $0
main.go:6) CALL runtime.makemap(SB)
main.go:6) MOVQ 24(SP), AX
- 通过汇编能发现map调用的是runtime/map.go 包下的makemap 方法
func makemap(t *maptype, hint int, h *hmap) *hmap {
mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
hint = 0
}
// initialize Hmap
if h == nil {
h = new(hmap)
}
h.hash0 = fastrand()
// Find the size parameter B which will hold the requested # of elements.
// For hint < 0 overLoadFactor returns false since hint < bucketCnt.
B := uint8(0)
for overLoadFactor(hint, B) {
B++
}
h.B = B
// allocate initial hash table
// if B == 0, the buckets field is allocated lazily later (in mapassign)
// If hint is large zeroing this memory could take a while.
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
}
- 上述方法结构示意图如下:
3.2 通过字面量初始化
3.2.1 元素小于25个时,内部转化为简单赋值
1) code
func main() {
h := map[string]int{
"1": 2,
"2": 2,
"3": 3,
}
}
2)汇编代码底层其实是这样子的
func main() {
h := make(map[string]int, 3)
h["1"] = 1
h["2"] = 2
h["3"] = 3
}
3.2.2 元素多于25个,转换为循环赋值
1)code
func main() {
h := map[string]int{
"1": 2,
"2": 2,
"3": 3,
...
"26" : 26
}
}
2)实际上
4 map 的访问
- 步骤1
- 计算桶号
- 计算tophash
- 匹配
5. map 写入
总结
- go 语言中使用拉链法实现了hashmap
- 每一个桶中存储键哈希的前8位
- 桶超出8个数据,就会存储到溢出桶中