golang map 底层部分理解

1,643 阅读2分钟

部分参考链接

bucket理解

初始的bucket数量通过

// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
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
}

即个数由传入的参数hint,通过overLoadFactor函数计算得来一个B,B决定了初始bucket数量,这些buckets以数组形式存储,每一个bucket组成如下:

  • 一个包含8个uint8数组,对应存储的kv的key
  • key1key2...key8val1val2...val8
  • 指向下一个bucket的指针
  • 每一个map对应的maptype类型中的bucketsize的实现位于reflect包的type.go文件中,具体代码如下:
func MapOf(key, elem Type) Type {
	// Make a map type. 即编译器通过这里生成了对应该map的maptype
	var imap interface{} = (map[unsafe.Pointer]unsafe.Pointer)(nil)
	mt := **(**mapType)(unsafe.Pointer(&imap))
	mt.key = ktyp
	mt.elem = etyp
	mt.bucket = bucketOf(ktyp, etyp)
	mt.bucketsize = uint16(mt.bucket.size)  // bucketsize 在这里赋值,其中mt.bucket的结构bucketof()见下文

	ti, _ := lookupCache.LoadOrStore(ckey, &mt.rtype)
	return ti.(Type)
}

bucketof()定义如下(位于reflect包的type.go中):

func bucketOf(ktyp, etyp *rtype) *rtype {
	// See comment on hmap.overflow in ../runtime/map.go.

	// Prepare GC data if any.
	// A bucket is at most bucketSize*(1+maxKeySize+maxValSize)+2*ptrSize bytes,
	// or 2072 bytes, or 259 pointer-size words, or 33 bytes of pointer bitmap.
	// Note that since the key and value are known to be <= 128 bytes,
	// they are guaranteed to have bitmaps instead of GC programs.
	var gcdata *byte
	var ptrdata uintptr
	var overflowPad uintptr

        // 这里即为bucketsize的最终实现,8字节的hashtop,所有的key value占用空间,可能的pad填充,指向下一个bucket的指针
	size := bucketSize*(1+ktyp.size+etyp.size) + overflowPad + ptrSize

	b := &rtype{
	    align:   ptrSize,
	    size:    size,
	    kind:    kind,
    	ptrdata: ptrdata,
    	gcdata:  gcdata,
	}

	s := "bucket(" + ktyp.String() + "," + etyp.String() + ")"
	b.str = resolveReflectName(newName(s, "", false))
	return b
}

指向下一个bucket的指针赋值方式

func (h *hmap) newoverflow(t *maptype, b *bmap) *bmap {
	var ovf *bmap
	if h.extra != nil && h.extra.nextOverflow != nil {
    	// We have preallocated overflow buckets available.
    	// See makeBucketArray for more details.
    	ovf = h.extra.nextOverflow
    	if ovf.overflow(t) == nil {
    		// We are not at the end of the preallocated overflow buckets. Bump the pointer.
    		h.extra.nextOverflow = (*bmap)(add(unsafe.Pointer(ovf), uintptr(t.bucketsize)))
    	} else {
    		// This is the last preallocated overflow bucket.
    		// Reset the overflow pointer on this bucket,
    		// which was set to a non-nil sentinel value.
    		ovf.setoverflow(t, nil)
    		h.extra.nextOverflow = nil
    	}
	} else {
    	ovf = (*bmap)(newobject(t.bucket))
	}
	h.incrnoverflow()
	if t.bucket.kind&kindNoPointers != 0 {
    	h.createOverflow()
    	*h.extra.overflow = append(*h.extra.overflow, ovf)
	}
	b.setoverflow(t, ovf) // 最终通过这里的setoverflow将新建的bucket绑定到b的后面
	return ovf
}

func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
    // 减去的sys.PtrSize即为链接到后一个bucket的指针的大小,这样得到的刚好是该指针的地址
	*(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize)) = ovf
}

// 取overflow同样的道理
func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
	*(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize)) = ovf
}

持续更新中......