Skip to content

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:

go
map[keyType]valueType{}

Birkaç örnek:

go
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:

go
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.

go
func main() {
   var mp map[string]int
   mp["a"] = 1
   fmt.Println(mp)
}
panic: assignment to entry in nil map

TIP

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.

go
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
0

Koddan 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:

go
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:

go
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:

go
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:

go
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:

go
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

go
func delete(m map[Type]Type1, key Type)

Anahtar-değer çiftini silmek için yerleşik delete fonksiyonu kullanılır, örneğin:

go
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.

go
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:

go
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 1

Sonucun 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:

go
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 b

Temizleme

go1.21 öncesinde, map'i temizlemek için her map anahtarını delete etmeniz gerekiyordu:

go
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:

go
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.

go
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.

go
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 writes

Bu durumda, sync.Map kullanarak değiştirmeniz gerekir.

Golang by www.golangdev.cn edit