在原本的bucket1后面再建一个bucket1*,再将这个key-value存进去。

上面至于为什么不进行扩容,没办法,没满足要求:

func overLoadFactor(count int, B uint8) bool {
    return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)}

map的数据量count大于(2^B)*6.5。注意这里不包括溢出的桶。

这里补充插入时的一些操作:

if h.flags&hashWriting != 0 {
        throw("concurrent map writes")
    }

这就是为什么同时读写map为什么会报错的原因了,加锁就行。

基本了解了数据结构,接着说扩容,还是上面的例子,B=1,扩容因为6.5,当key-value的数量达到13的时候(包括已经删除的),会触发扩容,B=B+1,容量扩大了一倍。为什么会包括已经删除的,其实这里描述的不太准确,mapdelete里面的具体操作是这样的:

为什么这么做:

这种删除方式,以少量空间来避免桶链表和桶内的数据移动。事实上,go 数据一旦被插入到桶的确切位置,map是不会再移动该数据在桶中的位置了。

那么这个删除模式会导致一种情况,就是每个桶里面只有一个数据,造成了很大的空间浪费了,想想map只增不减情况,这时候就需要缩容了。go里面也有缩容判断,不过这个缩容是伪缩容,

func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
    if B > 15 {
        B = 15
    }
    return noverflow >= uint16(1)<<(B&15) //溢出的桶数量noverflow>=32768(1<<15)}

只有溢出桶太多才会缩容,不过内存大小并不会发生变化,至于为什么不会变化,因为B没有变,想要真正实现内存的优化也是可行的,不过并不会太优雅。这时候就需要我们需要手动缩容了,说这么多,其实代码很简单:

oldMap := make(map[int]int, 100000)newMap := make(map[int]int, len(oldMap))for k, v := range oldMap {
    newMap[k] = v}oldMap = newMap

map还有一个比较有意思的是for range,可以了解一下