1.map的底层其实就是hashmap,开发中用于存储简直对。不多说直接上源码hamp
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
- count:键值对的数量(len去长度时候直接读取这个值)
- flags:标识位,记录map状态,例如正在写入、等待扩容等
- B:桶的对数为2的B次方
- noverflow:溢出桶的数量
- hash0:hash随机因子,后续对key进行hash使用
- buckets:桶数组
- oldbuckets:如果出现扩容情况则为老的桶数组
- nevacuate:扩容时的进度标识,index小于nevacuate的桶都已经由老桶转移到新桶中
- extra:预申请的溢出桶
type mapextra struct {
overflow *[]*bmap // 当前 buckets 的 overflow 列表
oldoverflow *[]*bmap // 扩容时旧 buckets 的 overflow 列表
nextOverflow *bmap // 用于管理溢出桶的分配
}
- overflow:值想当前buckets中所有的overflow bucket数组
- oldoverflow:扩容过程中,记录旧bucket的overflow bucket
- nextOverflow:指向下一个可以复用的溢出桶 2.接下来bmap
type bmap struct {
// 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 elems.
// NOTE: packing all the keys together and then all the elems together makes the
// code a bit more complicated than alternating key/elem/key/elem/... but it allows
// us to eliminate padding which would be needed for, e.g., map[int64]int8.
// Followed by an overflow pointer.
}
他只有一个tophash字段,还是看看完整的吧
type bmap struct {
tophash [bucketCnt]uint8
keys [bucketCnt]T
values [bucketCnt]T
overflow uint8
}
- tophash:每个键经过hash算法算出来的高8位
- keys:存储键本身
- values:存储值本身
- overflow:指向下一个bmap的指针
3.这里需要注意,每一个bmap也就是桶只会存储8个kve-value对,如果超出会进行创建申请一个新的bmap与之前的桶链起来形成一个链表。可以理解如果计算出来的位置命中同一个桶,但是桶已经装满的,这个是手就需要创建一个新的桶存储当前的值,并且使用指针他们串起来形成一个链表。
以上是hmap和bmap的一些解释,接下来看看他是如何进行储存和处理的
- 可以看看make的过程
// t map的类型信息
// hint 容量
func makemap(t *maptype, hint int, h *hmap) *hmap {
// hint 为map拟分配的容量
// t.bucket.size bucket的大小
// 进行安全的乘法元算 然后计算出所需内存以及合法性
mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
hint = 0
}
// initialize Hmap
if h == nil {
h = new(hmap)
}
// 初始化hash因子(种子) 防止hash冲突
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得值,不断的进行计算使得hint<=loadFactor*2^B
// overLoadFactor(hint, B)计算出负载因子大约在6.5最终算出B得值
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)
// 判断map容量是否够大 预分配溢出桶
if nextOverflow != nil {
h.extra = new(mapextra)
h.extra.nextOverflow = nextOverflow
}
}
return h
}
这里是与分配的容量、桶的长度指数、桶数组长度
| kv 对数量 | 桶数组长度指数 B | 桶数组长度 2^B |
|---|---|---|
| 0 ~ 8 | 0 | 1 |
| 9 ~ 13 | 1 | 2 |
| 14 ~ 26 | 2 | 4 |
| 27 ~ 52 | 3 | 8 |
| 2^(B-1) * 6.5+1 ~ 2^B*6.5 | B | 2^B |
- 接下来看看key是如何进行定位存储
- 首先key需要先进行hash计算之后,算出hash值,64位二进制
- 例如:1001011100001111011011001000111100101010001001011001010101001010
- 然后在之前make初始化的时候计算出B的值,去64位后B位值去确定当前key需要存放在哪个桶的
- 如果B的值为4则去后四位
1010这个时候就可以定位存放在第10个桶中 - 这个时候已经确定好需要放在哪个桶中,然后就需要确定放在桶中哪个位置
- 这个时候就需要取key通过hash计算出来的64位二进制的高8位
10010111来确定存放在bucket的哪个位置
总结一下:首先先需要对key进行hash计算,计算出来的hash值为64位,这个时候要先确定桶的位置,通过去取64位bit值后B位确定痛的位置。然后再取高8位去确定存放在bucket哪个位置存放。先确定桶位置,再去确定存放在桶里的位置。
-
来看看bmap
- 每个bmap只会存储8个key-value,而且是key单独存储,value单独存储。因为是为了节省空间、提高访问效率、适应不同类型的大小和对齐需求。
- 还有一个字段tophash,这里就是存储每个key通过hash计算出来的值取高8位、
- overflow存放的的一个指针,指向下一个溢出桶的指针
-
开放寻址法和拉链法,用于解决hash冲突。当发生hash冲突的时候会查看当前桶中是否还有凹槽可以插入,如果没有则就需要创建溢出桶然后把冲突的可key插入溢出桶中,然后使用指针进行关联,这个时候就出现链表的形式。