Go 語(yǔ)言 Map 如何順序讀?。?/h1>
Go 語(yǔ)言中的 map 是一種非常強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),它允許我們快速地存儲(chǔ)和檢索鍵值對(duì)。
然而,當(dāng)我們遍歷 map 時(shí),會(huì)有一個(gè)有趣的現(xiàn)象,那就是輸出的鍵值對(duì)順序是不確定的。
現(xiàn)象
先看一段代碼示例:
package main
import "fmt"
func main() {
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}
}
當(dāng)我們多執(zhí)行幾次這段代碼時(shí),就會(huì)發(fā)現(xiàn),輸出的順序是不同的。
原因
首先,Go 語(yǔ)言 map 的底層實(shí)現(xiàn)是哈希表,在進(jìn)行插入時(shí),會(huì)對(duì) key 進(jìn)行 hash 運(yùn)算。這也就導(dǎo)致了數(shù)據(jù)不是按順序存儲(chǔ)的,和遍歷的順序也就會(huì)不一致。
第二,map 在擴(kuò)容后,會(huì)發(fā)生 key 的搬遷,原來(lái)落在同一個(gè) bucket 中的 key,搬遷后,有些 key 可能就到其他 bucket 了。
而遍歷的過(guò)程,就是按順序遍歷 bucket,同時(shí)按順序遍歷 bucket 中的 key。
搬遷后,key 的位置發(fā)生了重大的變化,有些 key 被搬走了,有些 key 則原地不動(dòng)。這樣,遍歷 map 的結(jié)果就不可能按原來(lái)的順序了。
最后,也是最有意思的一點(diǎn)。
那如果說(shuō)我已經(jīng)初始化好了一個(gè) map,并且不對(duì)這個(gè) map 做任何操作,也就是不會(huì)發(fā)生擴(kuò)容,那遍歷順序是固定的嗎?
答:也不是。
Go 杜絕了這種做法,主要是擔(dān)心程序員會(huì)在開(kāi)發(fā)過(guò)程中依賴穩(wěn)定的遍歷順序,因?yàn)檫@是不對(duì)的。
所以在遍歷 map 時(shí),并不是固定地從 0 號(hào) bucket 開(kāi)始遍歷,每次都是從一個(gè)隨機(jī)值序號(hào)的 bucket 開(kāi)始遍歷,并且是從這個(gè) bucket 的一個(gè)隨機(jī)序號(hào)的 cell 開(kāi)始遍歷。
如何順序讀取
如果希望按照特定順序遍歷 map,可以先將鍵或值存儲(chǔ)到切片中,然后對(duì)切片進(jìn)行排序,最后再遍歷切片。
改造一下上面的代碼,讓它按順序輸出:
package main
import (
"fmt"
"sort"
)
func main() {
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
// 將 map 中的鍵存儲(chǔ)到切片中
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
// 對(duì)切片進(jìn)行排序
sort.Strings(keys)
// 按照排序后的順序遍歷 map
for _, k := range keys {
fmt.Printf("key=%s, value=%d\n", k, m[k])
}
}
在上面的代碼中,首先將 map 中的鍵存儲(chǔ)到一個(gè)切片中,然后對(duì)切片進(jìn)行排序。
最后,按照排序后的順序遍歷 map。這樣就可以按照特定順序輸出鍵值對(duì)了。