十個(gè)Go map面試常考問(wèn)題合集
1.Map 使用時(shí)需要注意哪些問(wèn)題
Map 的鍵必須是可比較的類型,如整數(shù)、字符串和指針等,但是切片、函數(shù)和結(jié)構(gòu)體等類型是不可比較的,因此不能用作鍵。
Map 中的元素是無(wú)序的,這意味著遍歷 Map 時(shí),元素的順序可能會(huì)隨機(jī)改變。
Map 的容量是動(dòng)態(tài)變化的,它會(huì)自動(dòng)調(diào)整容量以適應(yīng)新的元素。
如果使用未初始化的 Map,會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。需要使用 make() 函數(shù)來(lái)初始化 Map。
Map 在并發(fā)環(huán)境下不是安全的。如果需要在并發(fā)環(huán)境下使用 Map,需要使用 sync 包中提供的鎖機(jī)制來(lái)保護(hù) Map。
2.Map 擴(kuò)容是怎么實(shí)現(xiàn)的
在 Go 中,Map 的內(nèi)部實(shí)現(xiàn)是基于哈希表(hash table)的,因此,當(dāng) Map 中的鍵值對(duì)數(shù)量增加時(shí),Map 會(huì)自動(dòng)擴(kuò)容以提供更多的存儲(chǔ)空間。下面是 Go Map 擴(kuò)容的具體步驟:
- 當(dāng) Map 中的元素?cái)?shù)量超過(guò)了負(fù)載因子(load factor)和哈希表容量的乘積時(shí),map 就會(huì)觸發(fā)擴(kuò)容操作。在 Go 中,負(fù)載因子默認(rèn)為 6.5。
- Go Map 在擴(kuò)容時(shí)會(huì)創(chuàng)建一個(gè)新的哈希表,并將原來(lái)的鍵值對(duì)重新散列到新的哈希表中。為了減少哈希沖突,新哈希表的容量是原來(lái)的兩倍,并且容量一定是 2 的冪次方。即每次擴(kuò)容按照規(guī)則計(jì)算出新容量之后,為了內(nèi)存對(duì)齊,減少內(nèi)存碎片,會(huì)根據(jù)一個(gè)常量數(shù)字進(jìn)行向上取整。
- 在重新散列過(guò)程中,Go Map 會(huì)根據(jù)哈希值將原來(lái)的鍵值對(duì)分配到新哈希表中的對(duì)應(yīng)位置上。如果兩個(gè)鍵值對(duì)的哈希值相同,會(huì)使用鏈?zhǔn)焦1恚╟hained hash table)的方式進(jìn)行處理,將它們放在同一個(gè)桶(bucket)中。
- 一旦所有的鍵值對(duì)都已經(jīng)重新散列到新的哈希表中,Go Map 就會(huì)將原來(lái)的哈希表釋放掉,將新的哈希表作為 Map 的內(nèi)部存儲(chǔ)結(jié)構(gòu)。
注意: 由于擴(kuò)容操作可能會(huì)導(dǎo)致大量的重新散列操作,因此在擴(kuò)容時(shí)可能會(huì)造成一定的性能影響。為了避免頻繁擴(kuò)容,可以在創(chuàng)建 Map 時(shí)指定一個(gè)較大的容量,或者手動(dòng)調(diào)用 runtime.GC() 來(lái)觸發(fā)垃圾回收操作,以釋放未使用的內(nèi)存。
3. Map 的 panic 能被 recover 嗎
不能,并發(fā)讀寫(xiě) Map 也是很容易遇到的問(wèn)題
if?h.flags&hashWriting?!=?0?{ ??throw("concurrent?map?read?and?map?write") }
runtime.throw()
函數(shù)拋出異常是無(wú)法recover的
4. 并發(fā)使用 Map 除了加鎖還有什么其他方案嗎
除了加鎖之外,Go 并發(fā)使用 Map 的其他常見(jiàn)解決方案包括使用 sync.Map 和使用并發(fā)安全的第三方 Map 庫(kù)。
1、使用 sync.Map sync.Map 是 Go 1.9 新增的一種并發(fā)安全的 Map,它的讀寫(xiě)操作都是并發(fā)安全的,無(wú)需加鎖。使用 sync.Map 的示例代碼如下:
var?m?sync.Map m.Store("name",?"程序員祝融") value,?ok?:=?m.Load("name") if?ok?{ ????fmt.Println(value) } //?輸出程序員祝融
2、使用并發(fā)安全的第三方 Map 庫(kù) 除了使用 sync.Map,還可以使用其他第三方的并發(fā)安全的 Map 庫(kù),如 concurrent-map、ccmap 等。這些庫(kù)的使用方式與 Go 標(biāo)準(zhǔn)庫(kù)的 Map 類似,但它們都提供了更高效、更可靠的并發(fā)安全保證。
注意: 使用并發(fā)安全的第三方 Map 庫(kù)可能會(huì)導(dǎo)致額外的依賴和復(fù)雜性,因此需要仔細(xì)評(píng)估是否值得使用
5. sync.Map 和加鎖的區(qū)別是什么
sync.Map 和使用鎖的區(qū)別在于,sync.Map 不需要在并發(fā)訪問(wèn)時(shí)進(jìn)行加鎖和解鎖操作。相比之下,使用鎖需要在并發(fā)訪問(wèn)時(shí)顯式地加鎖和解鎖,以避免競(jìng)爭(zhēng)條件和數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
在使用鎖時(shí),當(dāng)多個(gè) goroutine 同時(shí)訪問(wèn)同一塊數(shù)據(jù)時(shí),必須通過(guò)加鎖來(lái)避免競(jìng)爭(zhēng)條件。這意味著只有一個(gè) goroutine 能夠訪問(wèn)該數(shù)據(jù),并且在該 goroutine 完成工作后,其他 goroutine 才能夠訪問(wèn)數(shù)據(jù)。這種方式可以確保數(shù)據(jù)的一致性,但是加鎖會(huì)帶來(lái)額外的開(kāi)銷,并且在高并發(fā)情況下可能會(huì)影響性能。
相比之下,sync.Map 使用了更高級(jí)的算法來(lái)避免競(jìng)爭(zhēng)條件和數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題,而不需要顯式地進(jìn)行加鎖和解鎖。當(dāng)多個(gè) goroutine 同時(shí)訪問(wèn) sync.Map 時(shí),它會(huì)自動(dòng)分配不同的段來(lái)存儲(chǔ)數(shù)據(jù),并且每個(gè)段都有自己的讀寫(xiě)鎖,以避免競(jìng)爭(zhēng)條件。這種方式可以提高并發(fā)性能,減少開(kāi)銷,并且避免死鎖等問(wèn)題。
6. Map 的查詢時(shí)間復(fù)雜度
Go 中的 Map 底層實(shí)現(xiàn)采用了哈希表,因此其查詢時(shí)間復(fù)雜度為 O(1),最壞情況為 O(n)。
7. Map Rehash 的策略是怎樣的 什么時(shí)機(jī)會(huì)發(fā)生 Rehash
當(dāng)哈希表中的元素?cái)?shù)量達(dá)到一定閾值時(shí),就會(huì)觸發(fā)哈希表的擴(kuò)容操作,也就是進(jìn)行 rehash。
Go 標(biāo)準(zhǔn)庫(kù)中的哈希表實(shí)現(xiàn)在以下情況下會(huì)觸發(fā) rehash 操作:
- 當(dāng)哈希表中的元素?cái)?shù)量超過(guò)了哈希表容量的 2/3 時(shí),會(huì)觸發(fā)擴(kuò)容操作。此時(shí),哈希表的容量會(huì)翻倍,并且哈希表中所有的元素都會(huì)重新哈希到新的槽位中。
- 當(dāng)哈希表中的元素?cái)?shù)量過(guò)少,而哈希表的容量過(guò)大時(shí),會(huì)觸發(fā)收縮操作。此時(shí),哈希表的容量會(huì)減半,并且哈希表中所有的元素都會(huì)重新哈希到新的槽位中。
- 當(dāng)哈希表的探測(cè)序列過(guò)長(zhǎng)時(shí),會(huì)觸發(fā)重排操作。此時(shí),哈希表中的元素不會(huì)重新哈希,但是它們的存儲(chǔ)位置會(huì)發(fā)生變化,從而減少聚簇現(xiàn)象,提高哈希表的性能。
在進(jìn)行 rehash 操作時(shí),哈希表會(huì)創(chuàng)建一個(gè)新的數(shù)組來(lái)存儲(chǔ)重新哈希后的元素,然后將舊數(shù)組中的元素逐個(gè)復(fù)制到新數(shù)組中。由于重新哈希的過(guò)程比較耗時(shí),因此 Go 標(biāo)準(zhǔn)庫(kù)中的哈希表實(shí)現(xiàn)采用了增量式 rehash 策略,在擴(kuò)容和收縮時(shí)只會(huì)處理一部分元素,避免一次性處理過(guò)多元素導(dǎo)致性能下降。具體來(lái)說(shuō),增量式 rehash 的策略是:
- 將新數(shù)組的容量設(shè)置為舊數(shù)組的兩倍或一半,并且將哈希表的增量計(jì)數(shù)器加一。
- 在對(duì)哈希表進(jìn)行操作時(shí),如果發(fā)現(xiàn)增量計(jì)數(shù)器的值達(dá)到了一個(gè)閾值,就會(huì)開(kāi)始進(jìn)行增量式 rehash 操作,將一部分元素從舊數(shù)組中復(fù)制到新數(shù)組中,并且重新計(jì)算這些元素的哈希值。
- 在完成一次增量式 rehash 操作后,會(huì)將哈希表的增量計(jì)數(shù)器清零。
通過(guò)增量式 rehash 的策略,Go 標(biāo)準(zhǔn)庫(kù)中的哈希表實(shí)現(xiàn)可以在保證較短的 rehash 時(shí)間的同時(shí),避免一次性處理過(guò)多元素導(dǎo)致性能下降
8. Map Rehash 具體會(huì)影響什么?哈希結(jié)果會(huì)受到什么影響
rehash 操作會(huì)影響 Map
的性能。由于重新計(jì)算鍵的哈希值,rehash 操作會(huì)消耗一定的計(jì)算資源。此外,在 rehash 過(guò)程中,原始哈希表的所有鍵值對(duì)都需要復(fù)制到新的哈希表中,因此 rehash 操作還會(huì)消耗一定的內(nèi)存空間和時(shí)間。
rehash 操作不會(huì)直接影響哈希結(jié)果。哈希結(jié)果是由哈希函數(shù)計(jì)算得出的,與 Map
中元素的數(shù)量和布局無(wú)關(guān)。rehash 操作只會(huì)影響哈希表的布局,即每個(gè)鍵在哈希表中的位置會(huì)發(fā)生變化,但是每個(gè)鍵的哈希值并不會(huì)改變。
9. Map Rehash 過(guò)程中存放在舊桶的元素如何遷移
rehash 操作會(huì)創(chuàng)建一個(gè)新的哈希表,同時(shí)保留舊的哈希表不變。然后,它會(huì)依次遍歷舊哈希表中的每個(gè)桶,將桶中的所有鍵值對(duì)復(fù)制到新哈希表中對(duì)應(yīng)的桶中。在遍歷每個(gè)桶時(shí),如果桶中的元素已經(jīng)被刪除,則跳過(guò)這些元素。
當(dāng)遍歷到一個(gè)桶時(shí),Map
實(shí)現(xiàn)會(huì)首先獲取舊哈希表中該桶的互斥鎖,以確保在復(fù)制元素時(shí)不會(huì)有并發(fā)訪問(wèn)的問(wèn)題。然后,它會(huì)遍歷桶中的所有鍵值對(duì),對(duì)于每個(gè)鍵值對(duì),它會(huì)計(jì)算鍵在新哈希表中的位置,并將鍵值對(duì)插入到對(duì)應(yīng)的桶中。插入過(guò)程中,如果新哈希表中對(duì)應(yīng)的桶已經(jīng)存在元素,則會(huì)將新元素插入到該桶的鏈表的尾部。
在整個(gè) rehash 過(guò)程中,新哈希表會(huì)保持為空,直到所有元素都被復(fù)制完畢。復(fù)制完成后,Map
實(shí)現(xiàn)會(huì)用新哈希表替換舊哈希表,并釋放舊哈希表占用的內(nèi)存空間。這樣,Map
中的所有元素都被遷移到了新哈希表中。
需要注意的是,在 rehash 過(guò)程中,如果有并發(fā)訪問(wèn) Map
,則可能會(huì)發(fā)生競(jìng)爭(zhēng)條件。因此,Go 語(yǔ)言中的 Map
不是線程安全的。如果需要在多個(gè) goroutine 中訪問(wèn) Map
,則需要使用互斥鎖或其他并發(fā)安全的機(jī)制來(lái)保護(hù)訪問(wèn)
10. sync.Map 的 Load() 方法流程
func (m *Map) Load(key any) (value any, ok bool) { read, _ := m.read.Load().(readOnly) e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() // Avoid reporting a spurious miss if m.dirty got promoted while we were // blocked on m.mu. (If further loads of the same key will not miss, it's // not worth copying the dirty map for this key.) read, _ = m.read.Load().(readOnly) e, ok = read.m[key] if !ok && read.amended { e, ok = m.dirty[key] // Regardless of whether the entry was present, record a miss: this key // will take the slow path until the dirty map is promoted to the read // map. m.missLocked() } m.mu.Unlock() } if !ok { return nil, false } return e.load() }
可見(jiàn)Load
函數(shù)直接從底層讀取元素,如果存在則返回,不管此時(shí)是否有寫(xiě)入操作。因?yàn)?code>e.load函數(shù)內(nèi)是通過(guò)原子操作讀取數(shù)據(jù)的。
因此當(dāng)key存在的時(shí)候,Load
函數(shù)不會(huì)加鎖。Stone
當(dāng)元素存在的時(shí)候也不會(huì)加鎖,而是通過(guò)atomic.CompareAndSwapPointer
修改元素的值。
所以Load
,Store
只有當(dāng)key不存在的時(shí)候才會(huì)加鎖,其他情況不會(huì)加鎖,因此它適合讀多寫(xiě)少的場(chǎng)景,速度非常快。
到此這篇關(guān)于十個(gè)Go map面試常考問(wèn)題合集的文章就介紹到這了,更多相關(guān)Go map內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言中validation庫(kù)不能校驗(yàn)零值問(wèn)題的解決方法
在使用 Gin 框架的時(shí)候,前后端傳遞數(shù)據(jù)的時(shí)候,比如使用 JSON 格式,通常會(huì)使用 ShouldBindJSON 去用結(jié)構(gòu)體打 tag 綁定前端傳來(lái)的 JSON 格式數(shù)據(jù),本文給大家介紹了Go語(yǔ)言中validation庫(kù)不能校驗(yàn)零值問(wèn)題的解決方法,需要的朋友可以參考下2024-08-08詳解Go語(yǔ)言中Get/Post請(qǐng)求測(cè)試
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的環(huán)境安裝以及Get和Post請(qǐng)求接口的測(cè)試,文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起學(xué)習(xí)一下2022-06-06Go語(yǔ)言omitempty選項(xiàng)的實(shí)現(xiàn)
本文主要介紹了Go語(yǔ)言omitempty選項(xiàng)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06重學(xué)Go語(yǔ)言之如何開(kāi)發(fā)RPC應(yīng)用
這篇文章主要為大家詳細(xì)介紹了在Go語(yǔ)言中如何構(gòu)建RPC應(yīng)用,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn)
本文主要介紹了golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn),在Iris里面,提供了一種方式可以讓我們同時(shí)運(yùn)行多個(gè)應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Apache?IoTDB開(kāi)發(fā)系統(tǒng)之Go原生接口方法
這篇文章主要為大家介紹了?Apache?IoTDB開(kāi)發(fā)系統(tǒng)之Go原生接口方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Golang設(shè)計(jì)模式之外觀模式的實(shí)現(xiàn)
這篇文章主要介紹了Golang設(shè)計(jì)模式之外觀模式的實(shí)現(xiàn),外觀模式是一種常用的設(shè)計(jì)模式之一,是一種結(jié)構(gòu)型設(shè)計(jì)模式,它提供了一個(gè)簡(jiǎn)單的接口來(lái)訪問(wèn)復(fù)雜系統(tǒng)的各種功能,從而降低了系統(tǒng)的復(fù)雜度,需要詳細(xì)了解可以參考下文2023-05-05