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函数, 其大体步骤如下:
- 使用hash 值定位元素在哪一个 Bucket 中
- 遍历 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