Go map

本文详细介绍了Go语言中Map的基础原理,包括其底层数据结构Hmap和Bmap的设计,以及扩容规则。在Go中,Map的负载因子默认为6.5,当元素数量超过桶容量的6.5倍时,会发生扩容。同时,文章还探讨了溢出桶的使用情况和初始化过程。通过对make函数的分析,展示了Map在内存中的分配方式。
摘要由CSDN通过智能技术生成

Go map

基础

import (
	"fmt"
	"testing"
)

// 声明map
var m = map[string]interface{}{}

// 初始化
func TestInitUseMap(t *testing.T)  {
	// 使用make来进行初始化
	m = make(map[string]interface{})
	// 增加
	m["key1"] = "v1"
	// 查询
	v,ok := m["key1"]
	if ok {
		fmt.Print(v)
	}
	// 更新
	m["key1"] = "v2"
	// 删除
	delete(m,"key1")
	// 遍历
	m["k"] = "v"
	for k,v := range m{
		fmt.Println(k,v)
	}
	// 函数传参中,map传递的是地址
}

原理

底层数据结构

在这里插入图片描述

map结构

// A header for a Go map.
type hmap struct {

	count     int   // 键值对数目
	flags     uint8 // 是否在扩容
	B         uint8  // 桶的数目,2的b次方
	noverflow uint16 // 溢出桶使用的数量
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // 桶指针
	oldbuckets unsafe.Pointer // 迁移过程中,指向还没迁移完成的旧桶
	nevacuate  uintptr        // 即将迁移的旧桶编号

	extra *mapextra // 扩展字段
}

在这里插入图片描述

Bmap结构:

  • 一个bmap就是一个桶,一个桶里可以放8对键值对
  • 为了使内存排列更加紧凑,8个key放一起,8个value放一起
  • overflow指针是一个指向溢出桶的指针,为了减少扩容次数,当8个健值对都放满后,对出来的kv就放在溢出桶,溢出桶内存布局和普通桶是一样的
  • 桶中还存放在tophash,hash值的高8位
    在这里插入图片描述
    bmap在go源码中结构很简单
type bmap struct {
	tophash 【bucketCnt】uint8
}

但是能根据编译期间的 cmd/compile/internal/gc.bmap 函数对它的结构重建

type bmap struct {
    topbits  【8uint8 // tophash
    keys     【8】keytype   // key数组,一起排列
    values   【8】valuetype // value数组,一起排列
    pad      uintptr    
    overflow uintptr    // 溢出桶指针
}

当哈希表要分配的桶的数目大于2的4次方(16)时,就认为使用到溢出桶的概率比较大,就会预分配2的b-4次方个溢出桶,这些预分配的溢出桶在内存中是和普通通连续排列在一起的。

上边说到的hmap中有一个extra扩展字段,里边是指向mapextra的指针,记录的是溢出桶的使用信息

在这里插入图片描述

// mapextra holds fields that are not present on all maps.
type mapextra struct {
	overflow    *[]*bmap // 是一个切片,记录已经使用的那些溢出桶
	oldoverflow *[]*bmap // 记录在扩容阶段存储旧桶用到的那些溢出桶的地址
	nextOverflow *bmap  // 指向下一个空闲溢出桶
}

扩容规则

go中map默认的负载因子是6.5,负载因子是等于元素个数/hash表容量,负载因子越大,说明空闲位置越少。当满足

count / 2^B > 6.5

就会发生翻倍扩容,map新桶数量是旧桶两倍

如果没有满足大于6.5负载因子,但是使用到了较多的溢出桶,也会发生扩容,但是是等量扩容,就是创建和旧桶数量一样的新桶,在做迁移

B <= 15 && noverflow >= 2^B
or
B > 15 && noverflow >= 2^15

初始化

func makemap(t *maptype, hint int, h *hmap) *hmap {
	// 边界检查
	mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
	if overflow || mem > maxAlloc {
		hint = 0
	}
    // 初始化map
	if h == nil {
		h = new(hmap)
	}
    // 生成hash种子
	h.hash0 = fastrand()
    // 计算得到合适的桶数量B
	B := uint8(0)
	for overLoadFactor(hint, B) {
		B++
	}
	h.B = B
    // 为桶分配空间,即初始化数组出来
	if h.B != 0 {
		var nextOverflow *bmap
		h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
		if nextOverflow != nil {
			h.extra = new(mapextra)
			h.extra.nextOverflow = nextOverflow
		}
	}

	return h
}

使用 makeBucketArray创建用于保存桶的数组,这个方法其实就是根据传入的 B 计算出的需要创建的桶数量在内存中分配一片连续的空间用于存储数据,在创建桶的过程中还会额外创建一些用于保存溢出数据的桶,数量是 2^(B-4) 个。初始化完成返回hmap指针。

func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
	base := bucketShift(b)
	nbuckets := base
	if b >= 4 {
		nbuckets += bucketShift(b - 4)
		sz := t.bucket.size * nbuckets
		up := roundupsize(sz)
		if up != sz {
			nbuckets = up / t.bucket.size
		}
	}

	if dirtyalloc == nil {
		buckets = newarray(t.bucket, int(nbuckets))
	} else {
		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)))
		last.setoverflow(t, (*bmap)(buckets))
	}
	return buckets, nextOverflow
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值