Golang Map

87 阅读5分钟

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的一些解释,接下来看看他是如何进行储存和处理的


  1. 可以看看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 ~ 801
9 ~ 1312
14 ~ 2624
27 ~ 5238
2^(B-1) * 6.5+1 ~ 2^B*6.5B2^B
  1. 接下来看看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哪个位置存放。先确定桶位置,再去确定存放在桶里的位置。

  1. 来看看bmap

    • 每个bmap只会存储8个key-value,而且是key单独存储,value单独存储。因为是为了节省空间、提高访问效率、适应不同类型的大小和对齐需求。
    • 还有一个字段tophash,这里就是存储每个key通过hash计算出来的值取高8位、
    • overflow存放的的一个指针,指向下一个溢出桶的指针
  2. 开放寻址法和拉链法,用于解决hash冲突。当发生hash冲突的时候会查看当前桶中是否还有凹槽可以插入,如果没有则就需要创建溢出桶然后把冲突的可key插入溢出桶中,然后使用指针进行关联,这个时候就出现链表的形式。