最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go map底層實現(xiàn)與擴容規(guī)則和特性分類詳細講解

 更新時間:2023年03月28日 11:27:43   作者:Mengo_x  
這篇文章主要介紹了Go map底層實現(xiàn)與擴容規(guī)則和特性,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧

1、哈希表

哈希表用來存儲鍵值對,通過 hash 函數(shù)把鍵值對散列到一個個桶(bucket)中。

Go 使用與運算,桶個數(shù) m,則編號 [0, m-1],把鍵的 hash 值與 m-1 與運算。為保證所有桶都會被選中,m 一定為 2 的整數(shù)次冪。這樣 m 的二進制表示一定只有一位為 1,m-1 的二進制表示一定是低于這一位的所有位均為 1。下文擴容規(guī)則有詳細樣例。

  • m=4 (00000100)
  • m-1 (00000011)

如果桶的個數(shù)不是2的整數(shù)次冪,就有可能出現(xiàn)有些桶絕對不會被選中的情況 :

  • m=5 (00000101)
  • m-1 (00000100)

則 [1, 3] 注定是空桶。

負載因子 = count / bucket數(shù)量

2、Go map底層實現(xiàn)

hmap

Golang的map就是使用哈希表作為底層實現(xiàn),map 實際上就是一個指針,指向hmap結(jié)構(gòu)體。

type hmap struct {
  count     int              // 存儲的鍵值對數(shù)目
  flags     uint8            // 狀態(tài)標志(是否處于正在寫入的狀態(tài)等)
  B         uint8            // 桶的數(shù)目 2^B
  noverflow uint16           // 使用的溢出桶的數(shù)量
  hash0     uint32           // 生成hash的隨機數(shù)種子
  buckets    unsafe.Pointer  // bucket數(shù)組指針,數(shù)組的大小為2^B(桶)
  oldbuckets unsafe.Pointer  // 擴容階段用于記錄舊桶用到的那些溢出桶的地址
  nevacuate  uintptr         // 記錄漸進式擴容階段下一個要遷移的舊桶編號
  extra *mapextra            // 指向mapextra結(jié)構(gòu)體里邊記錄的都是溢出桶相關的信息
}

bmap

buckets 則是指向哈希表節(jié)點 bmap 即 bucket 的指針,Go 中一個桶里面會最多裝 8 個 key。

hash 值低8位用來定位 bucket,高8位定位 tophash。

type bmap struct {
    tophash [bucketCnt]uint8        
    // len為8的數(shù)組,用來快速定位key是否在這個bmap中
    // 一個桶最多8個槽位,如果key所在的tophash值在tophash中,則代表該key在這個桶中
}

上面bmap結(jié)構(gòu)是靜態(tài)結(jié)構(gòu),在編譯過程中runtime.bmap會拓展成以下結(jié)構(gòu)體:

type bmap struct{
    topbits  [8]uint8
    keys     [8]keytype
    values   [8]valuetype
    pad      uintptr        // 內(nèi)存對齊使用,可能不需要
    overflow uintptr        // 當bucket 的8個key 存滿了之后
    // overflow 指向下一個溢出桶 bmap,
    // overflow是uintptr而不是*bmap類型,保證bmap完全不含指針,是為了減少gc,溢出桶存儲到extra字段中
}

tophash:是個長度為8的數(shù)組,哈希值低位相同的鍵存入當前bucket時會將哈希值的高 8 位存儲在該數(shù)組中,以方便后續(xù)匹配。

tophash字段不僅存儲key哈希值的高8位,還會存儲一些狀態(tài)值,用來表明當前桶單元狀態(tài),這些狀態(tài)值都是小于minTopHash的。為了避免key哈希值的高8位值和這些狀態(tài)值相等,產(chǎn)生混淆情況,所以當key哈希值高8位若小于minTopHash時候,自動將其值加上minTopHash作為該key的tophash。

emptyRest      = 0 // 表明此桶單元為空,且更高索引的單元也是空
emptyOne       = 1 // 表明此桶單元為空
evacuatedX     = 2 // 用于表示擴容遷移到新桶前半段區(qū)間
evacuatedY     = 3 // 用于表示擴容遷移到新桶后半段區(qū)間
evacuatedEmpty = 4 // 用于表示此單元已遷移
minTopHash     = 5 // key的tophash值與桶狀態(tài)值分割線值,小于此值的一定代表著桶單元的狀態(tài),大于此值的一定是key對應的tophash值
func tophash(hash uintptr) uint8 {
    top := uint8(hash >> (goarch.PtrSize*8 - 8))
    if top < minTopHash {
        top += minTopHash
    }
    return top
}

一個桶里邊可以放8個鍵值對,但是為了讓內(nèi)存排列更加緊湊,8個key放一起,8個value放一起,在8個key前面是8個tophash,每個tophash都是對應哈希值的高8位。

當key和value類型不一樣的時候,key和value占用字節(jié)大小不一樣,使用key/value這種形式可能會因為內(nèi)存對齊導致內(nèi)存空間浪費。

overflow:指向一個溢出桶,溢出桶的布局與常規(guī)的桶布局相同,是為了減少擴容次數(shù)引入的(即哈希沖突的拉鏈法)。當一個桶存滿了,還有可用的溢出桶時,就會在桶后邊鏈一個溢出桶繼續(xù)往里面存。

mapextra與溢出桶

如果哈希表要分配的桶的數(shù)目大與 **** 2 4 2^4 24**次方,就認為使用到溢出桶的幾率較大,就會預分配 2 ( B − 4 ) 2^{(B-4)} 2(B−4) 個溢出桶備用**,這些溢出桶與常規(guī)桶在內(nèi)存中是連續(xù)的,只是前 2 B 2^B 2B 個用作常規(guī)桶。

hmap 中最后有 extra 字段,它是指向mapextra結(jié)構(gòu)體,里邊記錄的都是溢出桶相關的信息。

type mapextra struct {
  overflow *[]*bmap     // 記錄已使用的溢出桶的地址
  oldoverflow *[]*bmap  // 擴容階段舊桶使用的溢出桶地址
  nextOverflow *bmap    // 指向下一個空閑溢出桶地址
}

如下圖所示,分配桶數(shù)目為 2 5 = 32 2^5 = 32 25=32,則備用溢出桶數(shù)目為 2 ( 5 − 4 ) = 2 2^{(5-4)} = 2 2(5−4)=2。

  • 此時編號為 2 的 bmap 桶存滿了,overflow 指向下一個溢出桶地址,這里指向 32 號。
  • hmapnoverflow 表示使用溢出桶數(shù)量,這里為 1。extra 字段指向記錄溢出桶的mapextra結(jié)構(gòu)體。
  • mapextra 中的 nextOverflow 指向下一個空閑溢出桶 33 號。

3、擴容規(guī)則

map擴容時使用漸進式擴容。

由于 map 擴容需要將原有的 key/value 重新搬遷到新的內(nèi)存地址,如果map存儲了數(shù)以億計的key-value,一次性搬遷將會造成比較大的延時,因此 Go map 的擴容采取了一種稱為**“漸進式”的方式,原有的 key 并不會一次性搬遷完畢,每次最多只會搬遷 2 個 bucket。只有在插入或修改、刪除 key 的時候,都會嘗試進行搬遷 buckets 的工作**。先檢查 oldbuckets 是否搬遷完畢,具體來說就是檢查 oldbuckets 是否為 nil。

翻倍擴容

count/(2^B) > 6.5:當負載因子超過6.5時就會觸發(fā)翻倍擴容。

如下圖,原來 B = 0,只有一個桶,裝滿后觸發(fā)翻倍擴容,B = 1,buckets 指向兩個新桶,oldbuckets 指向舊桶,nevacuate 表示接下來要遷移編號為 0 的舊桶。舊桶的鍵值對會漸進式分流到兩個新桶中。直到舊桶中的鍵值對全部搬遷完畢后,刪除oldbuckets。

遷移過程中使用與運算法hash & (m-1),把舊桶遷移到新桶上,用這個舊桶的hash值跟擴容后的桶的個數(shù) m-1 的值相與(&),得幾就在哪個位置上。

如果舊桶數(shù)量為4,那么新桶的數(shù)量就為 8。如果一個哈希值選擇 0 號舊桶,那么哈希值的二進制低兩位一定為 0。

舊桶 m-1 = 3 = 00000011,選擇 0 號舊桶說明哈希值為 xxxxxx00,00000011 & xxxxxx00 = 0

所以選擇新桶的結(jié)果只有兩種,取決于哈希值的第三位是 0還是 1。

新桶 m-1 = 7 = 00000111,與原哈希值與運算,若第三位是 0 則為 0,第三位為 1 則為 00000100 = 4。

等量擴容

雖然沒有超過負載因子限制,但是使用溢出桶過多,就會觸發(fā)等量擴容,創(chuàng)建和舊桶數(shù)目一樣多的新桶,然后把原來的鍵值對遷移到新桶中。

如果常規(guī)桶的數(shù)目小于等于 2 15 2^{15} 215 , 使用的溢出桶大于常規(guī)桶數(shù)目 2 B 2^B 2B就是多了。

B <= 15,noverflow >= 2^B

如果常規(guī)桶的數(shù)目大于 2 15 2^{15} 215 , 使用的溢出桶大于 2 15 2^{15} 215就是多了。

B > 15, noverflow >= 2^15

一般發(fā)生在很多鍵值對被刪除的情況下,這樣會造成overflow的bucket數(shù)量增多,但負載因子又不高。同樣數(shù)目的鍵值對,遷移到新桶中會把松散的鍵值對重新排列一次,使其排列的更加緊湊,進而保證更快的存取,這就是等量擴容的意義所在。

4、其他特性

map遍歷無序

使用 range 多次遍歷 map 時輸出的 key 和 value 的順序可能不同。這是 Go 語言的設計者們有意為之,旨在提示開發(fā)者們,Go 底層實現(xiàn)并不保證 map 遍歷順序穩(wěn)定,請大家不要依賴 range 遍歷結(jié)果順序。

主要原因有2點:

  • map在遍歷時,并不是從固定的0號bucket開始遍歷的,每次遍歷,都會從一個隨機值序號的bucket,再從其中隨機的cell開始遍歷
  • map遍歷時,是按序遍歷bucket,同時按需遍歷bucket中和其overflow bucket中的cell。但是map在擴容后,會發(fā)生key的搬遷,這造成原來落在一個bucket中的key,搬遷后,有可能會落到其他bucket中了,從這個角度看,遍歷map的結(jié)果就不可能是按照原來的順序了

map 本身是無序的,且遍歷時順序還會被隨機化,如果想順序遍歷 map,需要對 map key 先排序,再按照 key 的順序遍歷 map。

map非線程安全

Go 官方認為 Go map 更應適配典型使用場景(不需要從多個 goroutine 中進行安全訪問),而不是為了小部分情況(并發(fā)訪問),導致大部分程序付出加鎖代價(性能),決定了不支持,若并發(fā)讀寫 map 直接報錯。

官方推薦對 map 上讀寫鎖,一個匿名結(jié)構(gòu)(struct)體,包含一個原生和一個嵌入讀寫鎖 sync.RWMutex

var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}
counter.RLock()
n := counter.m["煎魚"]
counter.RUnlock()
counter.Lock()
counter.m["煎魚"]++
counter.Unlock()

map 的數(shù)據(jù)量非常大時,只有一把鎖會效率低下,分區(qū)見上鎖又邏輯復雜。Go1.9 起支持的 sync.Map,其支持并發(fā)讀寫 map。采取了 “空間換時間” 的機制,冗余了兩個數(shù)據(jù)結(jié)構(gòu),分別是:read 和 dirty,減少加鎖對性能的影響。

type Map struct {
   mu Mutex
   read atomic.Value // readOnly
   dirty map[interface{}]*entry
   misses int
}

其是專門為 append-only 場景設計的,也就是適合讀多寫少的場景。如果寫多性能會急劇下降。

到此這篇關于Go map底層實現(xiàn)與擴容規(guī)則和特性分類詳細講解的文章就介紹到這了,更多相關Go map底層實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對應的操作

    go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對應的操作

    這篇文章主要介紹了go語言通過反射創(chuàng)建結(jié)構(gòu)體、賦值、并調(diào)用對應的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • 如何判斷Golang接口是否實現(xiàn)的操作

    如何判斷Golang接口是否實現(xiàn)的操作

    這篇文章主要介紹了如何判斷Golang接口是否實現(xiàn)的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言對接微信支付與退款指南(示例詳解)

    Go語言對接微信支付與退款指南(示例詳解)

    在互聯(lián)網(wǎng)技術日益發(fā)展的背景下,Go語言憑借并發(fā)處理能力,在后端開發(fā)中大放異彩,本文詳細介紹如何使用Go語言對接微信支付,完成支付和退款功能,包括準備工作、初始化微信支付客戶端、實現(xiàn)支付功能,以及處理支付回調(diào)和退款等
    2024-10-10
  • 一文詳解kubernetes?中資源分配的那些事

    一文詳解kubernetes?中資源分配的那些事

    這篇文章主要為大家介紹了kubernetes?中資源分配的那些事,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • go語言使用中提示%!(NOVERB)的解決方案

    go語言使用中提示%!(NOVERB)的解決方案

    o語言的設計目標是提供一種簡單易用的編程語言,同時保持高效性和可擴展性,它支持垃圾回收機制,具有強大的并發(fā)編程能力,可以輕松處理大規(guī)模的并發(fā)任務,Go語言還擁有豐富的標準庫和活躍的開發(fā)社區(qū),使得開發(fā)者能夠快速構(gòu)建出高質(zhì)量的應用程序,需要的朋友可以參考下
    2023-10-10
  • golang 各種排序大比拼實例

    golang 各種排序大比拼實例

    這篇文章主要介紹了golang 各種排序大比拼實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang利用位運算實現(xiàn)為程序加速

    Golang利用位運算實現(xiàn)為程序加速

    這篇文章主要為大家詳細介紹了如何在Golang中利用位運算實現(xiàn)為程序加速功能,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下
    2022-08-08
  • go微服務PolarisMesh源碼解析服務端啟動流程

    go微服務PolarisMesh源碼解析服務端啟動流程

    這篇文章主要為大家介紹了go微服務PolarisMesh源碼解析服務端啟動流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • Go語言中new()和 make()的區(qū)別詳解

    Go語言中new()和 make()的區(qū)別詳解

    這篇文章主要介紹了Go語言中new()和 make()的區(qū)別詳解,本文講解了new 的主要特性、make 的主要特性,并對它們的區(qū)別做了總結(jié),需要的朋友可以參考下
    2014-10-10
  • Golang 串口通信的實現(xiàn)示例

    Golang 串口通信的實現(xiàn)示例

    串口通信是一種常見的硬件通信方式,用于在計算機和外部設備之間傳輸數(shù)據(jù),本文主要介紹了Golang 串口通信的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03

最新評論

汤原县| 荣昌县| 封开县| 应用必备| 刚察县| 凯里市| 许昌县| 苗栗市| 成武县| 乌拉特前旗| 武乡县| 沐川县| 黄大仙区| 大田县| 绥阳县| 彰化县| 长海县| 鱼台县| 友谊县| 建始县| 灵川县| 中牟县| 兴山县| 赣州市| 泸溪县| 京山县| 万安县| 凤冈县| 安丘市| 扬州市| 油尖旺区| 华亭县| 罗平县| 浏阳市| 孝感市| 沧州市| 鲁甸县| 瑞金市| 察哈| 渭南市| 蒙山县|