Go Map
Genel olarak, map veri yapısı uygulaması genellikle iki türdür: hash tablosu (hash table) ve arama ağacı (search tree). Aradaki fark前者 düzensizdir,后者 sıralıdır. Go'da map uygulaması hash bucket (bu da bir hash tablosudur) temelindedir, bu yüzden düzensizdir. Bu bölümde uygulama prensibi hakkında fazla açıklama yapılmayacaktır, bu temel kapsamını aşar, daha sonra derinlemesine analiz edilecektir.
TIP
Map prensibini öğrenmek için map uygulaması adresini ziyaret edebilirsiniz.
Başlatma
Go'da map anahtar türü karşılaştırılabilir olmalıdır, örneğin string, int karşılaştırılabilirdir, ancak []int karşılaştırılamaz, bu yüzden map anahtarı olarak kullanılamaz. Map başlatmanın iki yöntemi vardır, birincisi değişmez (literal), format şöyledir:
map[keyType]valueType{}Birkaç örnek:
mp := map[int]string{
0: "a",
1: "a",
2: "a",
3: "a",
4: "a",
}
mp := map[string]int{
"a": 0,
"b": 22,
"c": 33,
}İkinci yöntem yerleşik make fonksiyonunu kullanmaktır. Map için iki parametre alır: tür ve başlangıç kapasitesi. Örnek:
mp := make(map[string]int, 8)
mp := make(map[string][]int, 10)Map referans türüdür, sıfır değer veya başlatılmamış map erişilebilir, ancak öğe depolayamaz, bu yüzden mutlaka bellek tahsis edilmelidir.
func main() {
var mp map[string]int
mp["a"] = 1
fmt.Println(mp)
}panic: assignment to entry in nil mapTIP
Map başlatırken makul bir kapasite tahsis etmeniz önerilir, böylece genişletme sayısını azaltabilirsiniz.
Erişim
Map'e erişme yöntemi bir diziye indeksle erişmeye benzer.
func main() {
mp := map[string]int{
"a": 0,
"b": 1,
"c": 2,
"d": 3,
}
fmt.Println(mp["a"])
fmt.Println(mp["b"])
fmt.Println(mp["d"])
fmt.Println(mp["f"])
}0
1
3
0Koddan gözlemlenebileceği gibi, map'te "f" anahtarı olmasa bile, yine de bir dönüş değeri vardır. Map var olmayan anahtar için karşılık gelen türün sıfır değerini döndürür. Map'e erişirken aslında iki dönüş değeri vardır: birincisi karşılık gelen türün değeri, ikincisi bir boolean değerdir ve anahtarın var olup olmadığını temsil eder. Örnek:
func main() {
mp := map[string]int{
"a": 0,
"b": 1,
"c": 2,
"d": 3,
}
if val, exist := mp["f"]; exist {
fmt.Println(val)
} else {
fmt.Println("anahtar mevcut değil")
}
}Map'in uzunluğunu hesaplama:
func main() {
mp := map[string]int{
"a": 0,
"b": 1,
"c": 2,
"d": 3,
}
fmt.Println(len(mp))
}Değer Saklama
Map'e değer saklama yöntemi de diziye değer saklamaya benzer, örneğin:
func main() {
mp := make(map[string]int, 10)
mp["a"] = 1
mp["b"] = 2
fmt.Println(mp)
}Değer saklarken mevcut anahtar kullanılırsa, eski değerin üzerine yazılır:
func main() {
mp := make(map[string]int, 10)
mp["a"] = 1
mp["b"] = 2
if _, exist := mp["b"]; exist {
mp["b"] = 3
}
fmt.Println(mp)
}Ancak özel bir durum da vardır, anahtar math.NaN() olduğunda:
func main() {
mp := make(map[float64]string, 10)
mp[math.NaN()] = "a"
mp[math.NaN()] = "b"
mp[math.NaN()] = "c"
_, exist := mp[math.NaN()]
fmt.Println(exist)
fmt.Println(mp)
}false
map[NaN:c NaN:a NaN:b]Sonuçlardan gözlemlenebileceği gibi, aynı anahtar değeri üzerine yazmaz, aksine birden fazla var olabilir ve var olup olmadığını da判断 edemez, bu yüzden normal şekilde değer alamazsınız. Çünkü NaN IEE754 standardı tarafından tanımlanmıştır, uygulaması alt düzey汇编 komutu UCOMISD tarafından tamamlanır. Bu komut çift hassasiyetli ondalık sayıları düzensiz karşılaştırır ve NaN durumunu dikkate alır, bu yüzden sonuç herhangi bir sayının NaN'e eşit olmamasıdır, NaN kendi kendine de eşit değildir. Bu da her seferinde hash değerinin farklı olmasına neden olur. Bu konuda toplulukta şiddetli tartışmalar yaşanmıştır, ancak resmi tarafın değiştirilmesine gerek olmadığı düşünülmektedir, bu yüzden NaN'i map anahtarı olarak kullanmaktan kaçınmalısınız.
Silme
func delete(m map[Type]Type1, key Type)Anahtar-değer çiftini silmek için yerleşik delete fonksiyonu kullanılır, örneğin:
func main() {
mp := map[string]int{
"a": 0,
"b": 1,
"c": 2,
"d": 3,
}
fmt.Println(mp)
delete(mp, "a")
fmt.Println(mp)
}map[a:0 b:1 c:2 d:3]
map[b:1 c:2 d:3]Dikkat edilmesi gereken, eğer değer NaN ise, bu anahtar-değer çifti silinemez bile.
func main() {
mp := make(map[float64]string, 10)
mp[math.NaN()] = "a"
mp[math.NaN()] = "b"
mp[math.NaN()] = "c"
fmt.Println(mp)
delete(mp, math.NaN())
fmt.Println(mp)
}map[NaN:c NaN:a NaN:b]
map[NaN:c NaN:a NaN:b]Yineleme
for range kullanarak map'i yineleyebilirsiniz, örneğin:
func main() {
mp := map[string]int{
"a": 0,
"b": 1,
"c": 2,
"d": 3,
}
for key, val := range mp {
fmt.Println(key, val)
}
}c 2
d 3
a 0
b 1Sonucun sıralı olmadığı görülebilir, bu da map'in düzensiz depolandığını doğrular. Bahsetmeye değer bir nokta, NaN normal şekilde alınamasa da, yineleme ile erişilebilir. Örneğin:
func main() {
mp := make(map[float64]string, 10)
mp[math.NaN()] = "a"
mp[math.NaN()] = "b"
mp[math.NaN()] = "c"
for key, val := range mp {
fmt.Println(key, val)
}
}NaN a
NaN c
NaN bTemizleme
go1.21 öncesinde, map'i temizlemek için her map anahtarını delete etmeniz gerekiyordu:
func main() {
m := map[string]int{
"a": 1,
"b": 2,
}
for k, _ := range m {
delete(m, k)
}
fmt.Println(m)
}Ancak go1.21 clear fonksiyonunu güncelledi, artık önceki işlemi yapmanıza gerek yok, sadece bir clear ile temizleyebilirsiniz:
func main() {
m := map[string]int{
"a": 1,
"b": 2,
}
clear(m)
fmt.Println(m)
}Çıktı:
map[]Set
Set, tekrar eden öğeler içermeyen düzensiz bir koleksiyondur. Go'da buna benzer veri yapısı uygulaması sağlanmamıştır, ancak map'in anahtarları düzensiz ve tekrar edemez, bu yüzden map set yerine kullanılabilir.
func main() {
set := make(map[int]struct{}, 10)
for i := 0; i < 10; i++ {
set[rand.Intn(100)] = struct{}{}
}
fmt.Println(set)
}map[0:{} 18:{} 25:{} 40:{} 47:{} 56:{} 59:{} 81:{} 87:{}]TIP
Boş bir struct bellek kaplamaz.
Dikkat
Map eşzamanlı güvenli bir veri yapısı değildir. Go ekibi çoğu durumda map kullanımının yüksek eşzamanlılık senaryolarını içermediğini ve mutex eklemenin performansı büyük ölçüde düşüreceğini düşünmektedir. Map içinde okuma-yazma tespit mekanizması vardır, eğer çakışırsa fatal error tetiklenir. Örneğin aşağıdaki durumda fatal tetikleme olasılığı çok yüksektir.
func main() {
group.Add(10)
// map
mp := make(map[string]int, 10)
for i := 0; i < 10; i++ {
go func() {
// yazma işlemi
for i := 0; i < 100; i++ {
mp["helloworld"] = 1
}
// okuma işlemi
for i := 0; i < 10; i++ {
fmt.Println(mp["helloworld"])
}
group.Done()
}()
}
group.Wait()
}fatal error: concurrent map writesBu durumda, sync.Map kullanarak değiştirmeniz gerekir.
