golang中的map

0.1、索引

https://waterflow.link/articles/1666339004798

1、map的结构

map提供了键值对的无序集合,所有的键都是不重复的。在go中map是基于bmap数据结构的。在内部hash表是一个桶数组,每个桶是一个指向键值对数组的指针。每个桶里面可以保存8个元素。我们可以简化成下面的结构。

如果我们继续插入一个元素,hash键返回相同的索引,则另一个元素也会插入相同的桶中。
[图片上传失败...(image-85197a-1667465097178)]

如果正常桶中的元素已满,还有元素继续往相同的索引插入的话,go会创建另一个包含8个元素的桶并将前一个桶指向他。
[图片上传失败...(image-11c413-1667465097179)]

所以当我们读取、更新和删除map元素时,Go 必须计算相应的数组索引。 然后 Go 依次遍历所有键,直到找到提供的键。 因此,这三个操作的最坏情况时间复杂度为 O(p),其中 p 是桶中元素的总数(默认为一个桶,溢出时为多个桶)。

2、map初始化

首先我们先初始化一个包含3个元素的map:

m := map[string]int{
        "haha": 3,
        "hehe": 5,
        "hoho": 7,
    }

我们可能只需要遍历2个桶就可以找到上面的所有元素。

但是当我们添加100万个元素的时候,我们可能需要遍历上千个桶去找到指定的元素。

为了应对元素的增长,map会选择扩容,一般是当前桶数量增加一倍。那什么时候会扩容呢?

  • 负载因子大于6.5
  • 溢出桶太多

当map扩容的时候,所有的键都会重新分配到新的桶。所以最坏情况下,插入元素有可能是O(n)。

我们知道,在使用切片时,如果我们预先知道要添加到切片的元素数量,我们可以用给定的大小或容量对其进行初始化。这避免了不断应对切片增长导致底层数组频繁复制的问题。map与此类似。实际上,我们可以使用 make 内置函数在创建地图时提供初始大小。例如,如果我们要初始化一个包含 100 万个元素的map,可以这样写:

m := make(map[string]int, 1000000)

通过指定大小,go使用适当数量的桶创建map以存储 100 万个元素。 这节省了大量计算时间,因为map不用动态创建桶并处理桶溢出后rehash的问题。

指定大小 n 并不是说创建最多有100万个元素的map。 我们可以继续往map添加元素。 这实际代表着 Go 运行时至少 需要为n 个元素分配内存。

我们可以运行下基准测试看下这两个的性能差异:

package main

import (
    "testing"
)

var n = 1000000

func BenchmarkWithSize(b *testing.B) {
    for i := 0; i < b.N; i++ {
        m := make(map[string]int, n)
        for j := 0; j < n; j++ {
            m["hhs" + string(rune(j))] = j
        }
    }
}

func BenchmarkWithoutSize(b *testing.B) {
    for i := 0; i < b.N; i++ {
        m := make(map[string]int)
        for j := 0; j < n; j++ {
            m["hhs"+string(rune(j))] = j
        }
    }
}
go test -bench=.
goos: darwin
goarch: amd64
pkg: go-demo/5
cpu: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
BenchmarkWithSize-8                    6         178365104 ns/op
BenchmarkWithoutSize-8                 3         362949513 ns/op
PASS
ok      go-demo/5 4.563s

我们可以看到初始化map大小的性能是高于未设置初始化大小的性能。其中的原因上面应该解释的很清楚了。

3、map内存泄漏

我们看下下面的一个例子:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    n := 1000000
    m := make(map[int]struct{})
    printAlloc()

    for i := 0; i < n; i++ {
        m[i] = struct{}{}
    }
    printAlloc()

    for i := 0; i < n; i++ {
        delete(m, i)
    }

    runtime.GC()
    printAlloc()
    // 保留对m的引用,确保map不会被回收
    runtime.KeepAlive(m)

}

// 打印内存分配情况
func printAlloc() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%d MB\n", m.Alloc/1024/1024)
}
  1. 首先我们初始化一个map,map的值为空结构体,打印分配堆内存的大小。
  2. 接着我们往map中添加100万个元素,打印分配堆内存的大小。
  3. 然后我们删除所有元素,运行垃圾回收,打印分配堆内存的大小。

我们运行下上面的代码:

go run 5.go
0 MB
33 MB
21 MB

当我们添加100万元素之后,堆里面会分配33M的数据,像下面这样
[图片上传失败...(image-5a910b-1667465097179)]

当我们删除100万的数据之后,触发GC回收,实际上GC只是回收了桶里面的元素数据,桶的数量不会因为删除操作而减少,所以还有21M的数据
[图片上传失败...(image-9d5f1d-1667465097179)]

原因是map中的桶数不会缩小。

当然,为了解决大量写入、删除造成的内存泄漏问题,map引入了 sameSizeGrow 这一机制,在出现较多溢出桶时会整理哈希的内存减少空间的占用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容