ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Go map初始化和读取

2021-10-17 18:04:41  阅读:190  来源: 互联网

标签:map 初始化 uintptr unsafe bmap key Go Pointer size


数据结构

type hmap struct {
	count     int    //键值对的个数
	flags     uint8  //状态值
	B         uint8  //桶的幂数,桶的数量为2^B
	noverflow uint16 //溢出桶的数量
	hash0     uint32 // hash seed 哈希种子
	buckets    unsafe.Pointer // 哈希表数组,底层为[]bmap的首地址
	oldbuckets unsafe.Pointer // 旧哈希表数组,底层为[]bmap的首地址
	nevacuate  uintptr        // 当前迁移进度
	extra *mapextra // 溢出桶
}

溢出桶的管理

type mapextra struct {
    overflow    *[]*bmap
    oldoverflow *[]*bmap
    nextOverflow *bmap  //下一个空闲溢出桶
}

键值对存放在bmap上,bmap的数据结构
key值和value值是分别存放在一个长度为8的数组中,这样就不会因为内存填充的原因浪费内存

type bmap struct {
	tophash   [bucketCnt]uint8  //key高8位哈希值
	keys      [bucketCnt]key    //存放key,直接操作内存控制,结构体没有
	values    [bucketCnt]value //存放vaule,直接操作内存控制,结构体没有
	overflow   *bmap           //下一个溢出桶
}

以上是map核心数据结构,可以看成map将k-v键值,和对应的高8位哈希值对存放在bmap中,当产生哈希冲突时采用的拉链法解决哈希冲突,在bmap上链入下一个桶,这个桶称溢出桶的概念

初始化

//t map的类型,存放K和V的类型,这样就是可以确定bmap是存放多少字节了,计算方法是16+8*K+8*V
//就是(哈希高8位+K的内存大小+V的内存大小)*8+下一个溢出桶的地址

//hint是用户初始化的大小
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 {
	//初始化hmap,赋0值
		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
		//计算buckets的地址和空闲溢出桶的首地址
		h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
		if nextOverflow != nil {
			h.extra = new(mapextra)
			h.extra.nextOverflow = nextOverflow
		}
	}

	return h
}

计算长度

这也是计算是否需要扩容的条件

func overLoadFactor(count int, B uint8) bool {
//如果当前元素大于8个并且count>6.5*(2^B)则需要扩容,则B++
	return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

这里的疑问

  1. 是如果count<8则怎么办,这里是直接返回的
// 实际是返回1<<b ,即2^B
func bucketShift(b uint8) uintptr {
	// 用位运算就是为了解决溢出问题为0造成死循环问题b & (sys.PtrSize*8 - 1)
	return uintptr(1) << (b & (sys.PtrSize*8 - 1))
}

开启初始化桶

计算了桶的长度B后,如果B!=0开始初始化桶

//t map的类型,存放K和V的类型,这样就是可以确定bmap是存放多少字节了,计算方法是16+8*K+8*V
//就是(哈希高8位+K的内存大小+V的内存大小)*8+下一个溢出桶的地址

//b是用户初始化的大小
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
    //计算桶的长度2^b
	base := bucketShift(b)
	nbuckets := base
	//如果b的大小大于等于4
	if b >= 4 {
		//创建空闲溢出桶,追加溢出桶
		nbuckets += bucketShift(b - 4)
		//计算溢出桶+原本buckets的大小
		sz := t.bucket.size * nbuckets
		//重新分配规格内存,以GoLang的规则分配内存
		up := roundupsize(sz)
		if up != sz {
		    //计算总桶数(空闲溢出桶+2^B)
			nbuckets = up / t.bucket.size
		}
	}

	if dirtyalloc == nil {
	//分配内存空间,调用malloc函数,返回桶数组的地址
		buckets = newarray(t.bucket, int(nbuckets))
	} else {
		// dirtyalloc was previously generated by
		// the above newarray(t.bucket, int(nbuckets))
		// but may not be empty.
		buckets = dirtyalloc
		size := t.bucket.size * nbuckets
		if t.bucket.ptrdata != 0 {
			memclrHasPointers(buckets, size)
		} else {
			memclrNoHeapPointers(buckets, size)
		}
	}

   //如果有溢出桶
	if base != nbuckets {
		//获取第一个溢出桶的地址
		nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))
		//获取最后一个溢出桶的地址
		last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))
		//设置最后一个溢出桶中的下一个桶的*(bmap)设为第一个buckets的首地址,这样设置目的是表示没有空闲溢出了
		last.setoverflow(t, (*bmap)(buckets))
	}
	//返回buckets的地址,和溢出桶的地址
	return buckets, nextOverflow
}

重新计算内存大小

// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
	if size < _MaxSmallSize {
	//通过查表法找比size大的内存
		if size <= smallSizeMax-8 {
			return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
		} else {
			return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
		}
	}
	if size+_PageSize < size {
		return size
	}
	//
	return alignUp(size, _PageSize)
}

//返回(n+2^(13)-1)低13位摸0的的值
func alignUp(n, a uintptr) uintptr {
	return (n + a - 1) &^ (a - 1)
}

计算p+x的地址

func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
	return unsafe.Pointer(uintptr(p) + x)
}

设置bmap中overflow的值

func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
//获取b.overflow的地址,其内容的类型是*bmap,则其地址类型为*(*bmap),在赋值为ovf
//相当于b.overflow=ovf
	*(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize)) = ovf
}

初始化map的代码已经分析完了

map的读取

以string为key为主

func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
	if raceenabled && h != nil {
		callerpc := getcallerpc()
		racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess1_faststr))
	}
	if h == nil || h.count == 0 {
	//如果为空值或元素个数为0时则返回0值
		return unsafe.Pointer(&zeroVal[0])
	}
	if h.flags&hashWriting != 0 {
	//不支持并发写读
		throw("concurrent map read and map write")
	}
	//获取字符串类型
	key := stringStructOf(&ky)
	if h.B == 0 {
		// One-bucket table.
		//如果只有一个桶
		b := (*bmap)(h.buckets)
		if key.len < 32 {
			// short key, doing lots of comparisons is ok
			for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
			//如果key的长度小于32则直接字符串匹配或比较地址值
				k := (*stringStruct)(kptr)
				if k.len != key.len || isEmpty(b.tophash[i]) {
					if b.tophash[i] == emptyRest {
						break
					}
					continue
				}
				if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
					return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.elemsize))
				}
			}
			return unsafe.Pointer(&zeroVal[0])
		}
		// long key, try not to do more comparisons than necessary
		keymaybe := uintptr(bucketCnt)
		//keymaybe为潜在值为一样的下标
		for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
			k := (*stringStruct)(kptr)
			if k.len != key.len || isEmpty(b.tophash[i]) {
			//跳过长度不一样的key
				if b.tophash[i] == emptyRest {
					break
				}
				continue
			}
			if k.str == key.str {
			//如果地址一样则直接返回该bmap中k对应v的地址值
				return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.elemsize))
			}
			// check first 4 bytes
			if *((*[4]byte)(key.str)) != *((*[4]byte)(k.str)) {
				continue
			}
			// check last 4 bytes
			if *((*[4]byte)(add(key.str, uintptr(key.len)-4))) != *((*[4]byte)(add(k.str, uintptr(key.len)-4))) {
				continue
			}
			if keymaybe != bucketCnt {
			//如果找到两个潜在字符串key则在dohash处理
				// Two keys are potential matches. Use hash to distinguish them.
				goto dohash
			}
			//如果是第一个,并且前4个字节一样并且后4个字节也一样,则是潜在相同key
			keymaybe = i
		}
		if keymaybe != bucketCnt {
		    //找到潜在相同key
			k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+keymaybe*2*sys.PtrSize))
			//比较两个字符串是否相同
			if memequal(k.str, key.str, uintptr(key.len)) {
				return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+keymaybe*uintptr(t.elemsize))
			}
		}
		//找不到则返回0值
		return unsafe.Pointer(&zeroVal[0])
	}
dohash:
//获取key的哈希值
	hash := t.hasher(noescape(unsafe.Pointer(&ky)), uintptr(h.hash0))
	//获取索引掩码
	m := bucketMask(h.B)
	//计算该key对应bucket中bmap的地址
	b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
	if c := h.oldbuckets; c != nil {
	//如果此时正在扩容
		if !h.sameSizeGrow() {
			// There used to be half as many buckets; mask down one more power of two.     //如果不是等量扩容则旧桶数组长度是原来的一半
			m >>= 1
		}
		//计算旧桶数组的地址
		oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
		if !evacuated(oldb) {
		 //如果没有迁移
			b = oldb
		}
	}
	//获取高位哈希值
	top := tophash(hash)
	for ; b != nil; b = b.overflow(t) {
	//一直遍历当前索引的桶,直到后面没有溢出桶
		for i, kptr := uintptr(0), b.keys(); i < bucketCnt; i, kptr = i+1, add(kptr, 2*sys.PtrSize) {
		//在当前桶内遍历
			k := (*stringStruct)(kptr)
			if k.len != key.len || b.tophash[i] != top {
				continue
			}
			if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
			//如果匹配成功则返回对应value的地址值
				return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.elemsize))
			}
		}
	}
	return unsafe.Pointer(&zeroVal[0])
}

标签:map,初始化,uintptr,unsafe,bmap,key,Go,Pointer,size
来源: https://blog.csdn.net/weixin_42873876/article/details/120798460

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有