前言
Gomapmap
map
func main() {
	m := make(map[int][128]byte)
	for i := 0; i < 100000; i++ {
		b := [128]byte{}
		for j := 0; j < 128; j++ {
			b[j] = byte(j + 1)
		}
		m[i] = b
	}
	// 打印堆内存
	var ms runtime.MemStats
	runtime.ReadMemStats(&ms)
	fmt.Printf("堆内存: %d B, map size: %d\n", ms.Alloc, len(m))
	// 释放 map
	for i := 0; i < 100000; i++ {
		delete(m, i)
	}
	runtime.ReadMemStats(&ms)
	fmt.Printf("堆内存(释放后): %d B, map size: %d\n", ms.Alloc, len(m))
	// 手动触发 GC
	runtime.GC()
	runtime.ReadMemStats(&ms)
	fmt.Printf("堆内存(GC): %d B, map size: %d\n", ms.Alloc, len(m))
	// 保存 map 的引用,防止 GC 回收
	runtime.KeepAlive(m)
}

老规矩, 还是先来说说是个什么现象(本文所有例子, 基于 Go1.18)). 如果你运行这个程序, 那么会得到这样的结果:

堆内存: 32197640 B, map size: 100000
堆内存(释放后): 32198752 B, map size: 0
堆内存(GC): 21113120 B, map size: 0

mapGC20MGomap
探究

gdb 分析

mapGogdbmptype m

type = struct hash<int,[128]uint8> {
int count;
uint8 flags;
uint8 B;
uint16 noverflow;
uint32 hash0;
bucket<int,[128]uint8> *buckets;
bucket<int,[128]uint8> *oldbuckets;
uintptr nevacuate;
runtime.mapextra *extra;
}

hashhmap
print *m

初始化之前:

{count = 0, flags = 0 ‘\000’, B = 0 ‘\000’, noverflow = 0, hash0 = 2403831944, buckets = 0xc00013e380, oldbuckets = 0x0, nevacuate = 0, extra = 0x0}

初始化之后:

{count = 100000, flags = 0 ‘\000’, B = 14 ‘\016’, noverflow = 2679, hash0 = 4095224677, buckets = 0xc001540000, oldbuckets = 0x0, nevacuate = 8192, extra = 0xc00012c018}

delete 所有内容之后:

{count = 0, flags = 0 ‘\000’, B = 14 ‘\016’, noverflow = 2679, hash0 = 112371461, buckets = 0xc001540000, oldbuckets = 0x0, nevacuate = 8192, extra = 0xc00012c018}

GC 之后:

{count = 0, flags = 0 ‘\000’, B = 14 ‘\016’, noverflow = 2679, hash0 = 112371461, buckets = 0xc001540000, oldbuckets = 0x0, nevacuate = 8192, extra = 0xc00012c018}

countmap

map原理

JavaHashMaphash
map
BucketKVhashBucket 列表
overflow BucketBucket
BucketK/VK/Vmap[int8]int64

插入

插入操作通过调用mapassign函数, 其大体步骤如下:

  1. 使用hash 值定位元素在哪一个 Bucket 中
  2. 遍历 Bucket 中的元素, 找到第一个空位, 将数据插入

如何处理 hash 冲突

Gohash
bucketBucketextraBucketK/V

如何快速找到空位

Bucketkeyhash

扩容机制

Gomap

查看修改的操作, 在这里就不赘述了, 按照插入的流程寻找元素即可.

删除

map
key/valuecount

结构体

hmap
type hmap struct {
	count     int // 当前 map 中元素个数, len 函数用的就是它
	flags     uint8 // 标志位
	B         uint8  // 指数, 标识当前桶的个数为 2^B
	noverflow uint16 // 溢出桶的大致数目
	hash0     uint32 // 随机种子

	buckets    unsafe.Pointer // Bucket 数组指针
	oldbuckets unsafe.Pointer // 数组扩容时迁移过程中指向就地址的
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // 用来组成 Bucket 链表的内容
}
type mapextra struct {
	overflow    *[]*bmap // 指向溢出Bucket的地址
	oldoverflow *[]*bmap // 同上, 迁移过程使用
	nextOverflow *bmap // 指向第一个空闲的 Bucket, 追加时可快速获取到
}

解惑

Gomapkey/valuemapmap
mapmapvalueGC
mapmap[int][1024]bytemap[int]*[128]byteGCvalueGo
解决
map
mapmap

我简单想了几种方案:

mapmapvaluemapvaluevalueGCmap[int]*[128]bytemapGCmap38M
map