Golang中map縮容的實(shí)現(xiàn)
基本分析
在 Go 底層源碼 src/runtime/map.go 中,擴(kuò)縮容的處理方法是 grow 為前綴的方法來(lái)處理的。
其中擴(kuò)縮容涉及到的是插入元素的操作,對(duì)應(yīng) mapassign 方法:
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer { ... if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) { hashGrow(t, h) goto again } ... } func (h *hmap) growing() bool { return h.oldbuckets != nil } func overLoadFactor(count int, B uint8) bool { return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen) } func tooManyOverflowBuckets(noverflow uint16, B uint8) bool { if B > 15 { B = 15 } return noverflow >= uint16(1)<<(B&15) }
核心看到針對(duì)擴(kuò)縮容的判斷邏輯:
當(dāng)前沒有在擴(kuò)容:條件為 oldbuckets 不為 nil。
是否可以進(jìn)行擴(kuò)容:條件為 hmap.count> hash 桶數(shù)量 (2^B)*6.5。其中 hmap.count 指的是map 的數(shù)據(jù)數(shù)目, 2^B 僅指 hash 數(shù)組的大小,不包含溢出桶。
是否可以進(jìn)行縮容:條件為溢出桶(noverflow)的數(shù)量 >= 32768(1<<15)。
可以關(guān)注到,無(wú)論是擴(kuò)容還是縮容,其都是由 hashGrow 方法進(jìn)行處理:
func hashGrow(t *maptype, h *hmap) { bigger := uint8(1) if !overLoadFactor(h.count+1, h.B) { bigger = 0 h.flags |= sameSizeGrow } ... }
若是擴(kuò)容,則 bigger 為 1,也就是 B+1。代表 hash 表容量擴(kuò)大 1 倍。不滿足就是縮容,也就是 hash 表容量不變。
可以得出結(jié)論:map 的擴(kuò)縮容的主要區(qū)別在于 hmap.B 的容量大小改變。而縮容由于 hmap.B 壓根沒變,內(nèi)存空間的占用也是沒有變化的。
帶來(lái)的隱患
這種方式其實(shí)是存在運(yùn)行隱患的,也就是導(dǎo)致在刪除元素時(shí),并不會(huì)釋放內(nèi)存,使得分配的總內(nèi)存不斷增加。如果一個(gè)不小心,拿 map 來(lái)做大 key/value 的存儲(chǔ),也不注意管理,很容易就內(nèi)存爆了。
也就是 Go 語(yǔ)言的 map 目前實(shí)現(xiàn)的是 “偽縮容”,僅針對(duì)溢出桶過(guò)多的情況。若是觸發(fā)縮容,hash 數(shù)組的占用的內(nèi)存大小不變(等量擴(kuò)容)。
若要實(shí)現(xiàn) ”真縮容“,Go Contributor @josharian 表示目前唯一可用的解決方法是:創(chuàng)建一個(gè)新的 map 并從舊的 map 中復(fù)制元素。
示例如下:
old := make(map[int]int, 9999999) new := make(map[int]int, len(old)) for k, v := range old { new[k] = v } old = new ...
為什么不支持縮容
下述內(nèi)容會(huì)主要基于如下兩個(gè) issues 和 proposal 來(lái)分析:
《runtime: shrink map as elements are deleted[1]》
《proposal: runtime: add way to clear and reuse a map's working storage[2]》
目前 map 的縮容處理起來(lái)比較棘手,最早的 issues 是 2016 年提出的,也有人提過(guò)一些提案,但都因?yàn)榉N種原因被拒絕了。
簡(jiǎn)單來(lái)講,就是沒有找到一個(gè)很好的方法實(shí)現(xiàn),存在明確的實(shí)現(xiàn)成本問(wèn)題,沒辦法很方便的 ”告訴“ Go 運(yùn)行時(shí),我要:
- 記得保留存儲(chǔ)空間,我要立即重用 map。
- 趕緊釋放存儲(chǔ)空間,map 從現(xiàn)在開始會(huì)小很多。
抽象來(lái)看癥結(jié)是:需要保證增長(zhǎng)結(jié)果在下一個(gè)開始之前完成,此處的增長(zhǎng)指的是 ”從小到大,從一個(gè)大小到相同大小,從大到小“ 的復(fù)雜過(guò)程。
這屬于一個(gè)多重 case,從而導(dǎo)致也就一直拖著,慢慢想。
到此這篇關(guān)于Golang中map縮容的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang map縮容 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang詳細(xì)講解常用Http庫(kù)及Gin框架的應(yīng)用
下面這篇文章主要給大家介紹了關(guān)于Golang常用的Http庫(kù)及Gin框架,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06解析Golang中的鎖競(jìng)爭(zhēng)問(wèn)題
這篇文章主要介紹了golang中的鎖競(jìng)爭(zhēng)問(wèn)題,本文通過(guò)實(shí)例代碼給大家詳細(xì)講解,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10go語(yǔ)言使用range來(lái)接收通道里面的數(shù)據(jù)
本文主要介紹了go語(yǔ)言使用range來(lái)接收通道里面的數(shù)據(jù),for ... range?循環(huán)會(huì)一直從通道中接收值,直到通道關(guān)閉并且所有值都被接收完畢,下面就來(lái)介紹一下,感興趣的可以了解一下2025-04-04Golang設(shè)計(jì)模式之生成器模式講解和代碼示例
生成器是一種創(chuàng)建型設(shè)計(jì)模式,使你能夠分步驟創(chuàng)建復(fù)雜對(duì)象,與其他創(chuàng)建型模式不同,生成器不要求產(chǎn)品擁有通用接口,這使得用相同的創(chuàng)建過(guò)程生成不同的產(chǎn)品成為可能,本文就通過(guò)代碼示例為大家詳細(xì)介紹Golang生成器模式,感興趣的同學(xué)可以參考下2023-06-06Go語(yǔ)言操作數(shù)據(jù)庫(kù)及其常規(guī)操作的示例代碼
這篇文章主要介紹了Go語(yǔ)言操作數(shù)據(jù)庫(kù)及其常規(guī)操作的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Go語(yǔ)言實(shí)現(xiàn)RSA加解密算法詳解
隨著互聯(lián)網(wǎng)的高速發(fā)展,人們對(duì)安全的要求也越來(lái)越高,加解密也變得越來(lái)越重要,本文主要為大家介紹了Go語(yǔ)言中實(shí)現(xiàn)RSA加解密與簽名驗(yàn)證算法,希望對(duì)大家有所幫助2023-06-06Beego中ORM操作各類數(shù)據(jù)庫(kù)連接方式詳細(xì)示例
這篇文章主要為大家介紹了Beego中ORM操作各類數(shù)據(jù)庫(kù)連接方式詳細(xì)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04