1. Haritalara Giriş

Go dilinde, bir harita (map) farklı türlerdeki anahtar-değer çiftlerini depolayabilen özel bir veri tipidir. Bu, Python'daki bir sözlüğe veya Java'daki bir HashMap'e benzer. Go'da bir harita, hızlı veri arama, güncelleme ve silme özelliklerine sahip olan bir karma tablo kullanılarak uygulanan yerleşik bir tiptir.

Özellikler

  • Referans Türü: Bir harita, oluşturulduktan sonra aslında altta yatan veri yapısına bir işaretçi (pointer) alır.
  • Dinamik Büyüme: Dilimler gibi, bir haritanın alanı statik değildir ve veri arttıkça dinamik olarak genişler.
  • Anahtarın Benzersizliği: Bir haritadaki her anahtar benzersizdir ve aynı anahtar kullanılarak bir değer depolanırsa, yeni değer mevcut olanı geçersiz kılar.
  • Sırasız Koleksiyon: Bir haritadaki elemanlar sırasızdır, bu nedenle harita her gezildiğinde anahtar-değer çiftlerinin sırası farklı olabilir.

Kullanım Alanları

  • İstatistikler: Anahtarın benzersizliği sayesinde tekrar etmeyen öğeleri hızlı bir şekilde sayın.
  • Önbelleğe Alma: Anahtar-değer çifti mekanizması, önbelleğe alma uygulamak için uygundur.
  • Veritabanı Bağlantı Havuzu: Veritabanı bağlantıları gibi bir dizi kaynağı yöneterek, kaynakların paylaşılmasına ve birden fazla istemci tarafından erişilmesine olanak tanır.
  • Yapılandırma Öğesi Depolama: Yapılandırma dosyalarından gelen parametreleri depolamak için kullanılır.

2. Harita Oluşturma

2.1 make Fonksiyonu ile Oluşturma

Bir harita oluşturmanın en yaygın yolu, make fonksiyonunu aşağıdaki sözdizimiyle kullanmaktır:

make(map[anahtarTürü]değerTürü)

Burada anahtarTürü, anahtarın türüdür ve değerTürü ise değerin türüdür. İşte belirli bir kullanım örneği:

// String anahtar türü ve integer değer türü içeren bir harita oluştur
m := make(map[string]int)

Bu örnekte, boş bir harita oluşturduk ve bu harita, string anahtarları ve integer değerleri depolamak için kullanılır.

2.2 Sembolik Söz Dizimi ile Oluşturma

make kullanmanın yanı sıra, aynı zamanda sembolik bir sözdizimi kullanarak bir harita oluşturup başlatılabilir, bu sözdizimiyle aynı anda bir dizi anahtar-değer çifti bildiren:

m := map[string]int{
    "elma": 5,
    "armut": 6,
    "muz": 3,
}

Bu, yalnızca bir harita oluşturmaz, aynı zamanda bu harita için üç anahtar-değer çifti de belirler.

2.3 Harita Başlatma İçin Düşünülmeler

Bir harita kullanırken, başlatılmamış bir haritanın sıfır değeri nil olduğuna ve bu noktada doğrudan anahtar-değer çiftleri depolayamayacağınıza veya bunun çalışma zamanı bir hataya neden olacağına dikkat etmek önemlidir. Herhangi bir işlemden önce onu başlatmak için make'ı kullanmalısınız:

var m map[string]int
if m == nil {
    m = make(map[string]int)
}
// Artık m'yi kullanmak güvenlidir

Ayrıca bir haritadaki bir anahtarın varlığını kontrol etmek için özel bir sözdizimi olduğunu unutmamak önemlidir:

değer, var := m["anahtar"]
if !var {
    // "anahtar" haritada bulunmuyor
}

Burada değer, verilen anahtarla ilişkilendirilen değerdir ve var, anahtarın haritada var olup olmadığına bağlı olarak true veya false olan bir boole değerdir.

3. Haritaya Erişme ve Değiştirme

3.1 Elemanlara Erişme

Go dilinde, bir haritadaki bir anahtara karşılık gelen değere anahtarı belirterek erişebilirsiniz. Eğer anahtar haritada varsa, ilgili değeri alırsınız. Ancak eğer anahtar haritada yoksa, değer türünün sıfır değerini alırsınız. Örneğin, tamsayıları depolayan bir haritada, eğer anahtar mevcut değilse, 0 dönecektir.

func main() {
    // Bir harita tanımla
    skorlar := map[string]int{
        "Alice": 92,
        "Bob": 85,
    }

    // Mevcut bir anahtara erişme
    aliceSkoru := skorlar["Alice"]
    fmt.Println("Alice'ın skoru:", aliceSkoru) // Çıktı: Alice'ın skoru: 92

    // Mevcut olmayan bir anahtara erişme
    eksikSkor := skorlar["Charlie"]
    fmt.Println("Charlie'nin skoru:", eksikSkor) // Çıktı: Charlie'nin skoru: 0
}

Dikkat edilmesi gereken nokta, "Charlie" anahtarının var olmaması durumunda bir hata oluşturmaz, bunun yerine integer sıfır değeri, 0, döner.

3.2 Anahtar Varlığını Kontrol Etme

Bazen sadece bir anahtarın var olup olmadığını, karşılık gelen değerinden bağımsız olarak bilmek isteyebiliriz. Bu durumda, harita erişiminin ikinci dönüş değerini kullanabilirsiniz. Bu mantıksal dönüş değeri, bize anahtarın haritada var olup olmadığını söyleyecektir.

func main() {
    scores := map[string]int{
        "Alice": 92,
        "Bob": 85,
    }

    // "Bob" anahtarının var olup olmadığını kontrol etme
    score, exists := scores["Bob"]
    if exists {
        fmt.Println("Bob'ın puanı:", score)
    } else {
        fmt.Println("Bob'ın puanı bulunamadı.")
    }

    // "Charlie" anahtarının var olup olmadığını kontrol etme
    _, exists = scores["Charlie"]
    if exists {
        fmt.Println("Charlie'nin puanı bulundu.")
    } else {
        fmt.Println("Charlie'nin puanı bulunamadı.")
    }
}

Bu örnekte, bir anahtarın var olup olmadığını belirlemek için bir if ifadesi kullanırız.

3.3 Eleman Eklemek ve Güncellemek

Bir haritaya yeni elemanlar eklemek ve mevcut elemanları güncellemek aynı sözdizimini kullanır. Eğer anahtar zaten varsa, orijinal değer yeni değerle değiştirilecektir. Eğer anahtar yoksa, yeni bir anahtar-değer çifti eklenir.

func main() {
    // Boş bir harita tanımlama
    scores := make(map[string]int)

    // Eleman eklemek
    scores["Alice"] = 92
    scores["Bob"] = 85

    // Elemanları güncelleme
    scores["Alice"] = 96  // Mevcut bir anahtarı güncelleme

    // Haritayı yazdır
    fmt.Println(scores)   // Çıktı: map[Alice:96 Bob:85]
}

Ekleme ve güncelleme işlemleri özeldir ve basit bir atama ile gerçekleştirilebilir.

3.4 Eleman Silme

Bir haritadan elemanları kaldırmak için yerleşik delete işlevini kullanabilirsiniz. Aşağıdaki örnek silme işlemini göstermektedir:

func main() {
    scores := map[string]int{
        "Alice": 92,
        "Bob": 85,
        "Charlie": 78,
    }

    // Bir elemanı silme
    delete(scores, "Charlie")

    // Charlie'nin silindiğinden emin olmak için haritayı yazdırma
    fmt.Println(scores)  // Çıktı: map[Alice:92 Bob:85]
}

delete işlevi, ilk parametre olarak haritayı ve ikinci parametre olarak silinecek anahtarı alır. Eğer anahtar haritada bulunmuyorsa, delete işlevi etkisi olmaz ve hata fırlatmaz.

4. Haritayı Gezinme

Go dilinde, bir harita veri yapısını dolaşmak ve konteynerdaki her bir anahtar-değer çiftine erişmek için for range ifadesini kullanabilirsiniz. Bu tür bir döngü gezinme işlemi, harita veri yapısı tarafından desteklenen temel bir işlemdir.

4.1 for range Kullanarak Bir Harita Üzerinde Yineleme

for range ifadesi doğrudan bir harita üzerinde kullanılarak, haritadaki her bir anahtar-değer çiftini alabilirsiniz. Aşağıda bir harita üzerinde for range kullanarak temel bir örneği bulabilirsiniz:

package main

import "fmt"

func main() {
    myMap := map[string]int{"Alice": 23, "Bob": 25, "Charlie": 28}

    for key, value := range myMap {
        fmt.Printf("Anahtar: %s, Değer: %d\n", key, value)
    }
}

Bu örnekte, key değişkeni mevcut yinelemenin anahtarını alırken, value değişkeni o anahtara ait değeri alır.

4.2 Yineleme Sırası İçin Düşünülmeler

Bir harita üzerinde dolaşırken, döngü sırasının her zaman aynı olacağının bir garantisinin olmadığını belirtmek önemlidir, haritanın içeriği değişmese bile. Bu, Go'da bir harita üzerinde dolaşmanın rastgele olmasının programın belirli bir yineleme sırasına güvenmemesini sağlamak amacıyla tasarlandığını gösterir, böylece kodun kararlılığını artırır.

Örneğin, aşağıdaki kodu ardışık olarak iki kez çalıştırmak farklı çıktılar verebilir:

package main

import "fmt"

func main() {
    myMap := map[string]int{"Alice": 23, "Bob": 25, "Charlie": 28}

    fmt.Println("İlk yineleme:")
    for key, value := range myMap {
        fmt.Printf("Anahtar: %s, Değer: %d\n", key, value)
    }

    fmt.Println("\nİkinci yineleme:")
    for key, value := range myMap {
        fmt.Printf("Anahtar: %s, Değer: %d\n", key, value)
    }
}

Haritalarla İlgili 5 İleri Konu

Şimdi, haritalarla ilgili daha iyi anlamanıza ve kullanmanıza yardımcı olabilecek birkaç ileri konuya dalacağız.

5.1 Haritaların Bellek ve Performans Özellikleri

Go dilinde haritalar çok esnek ve güçlü bir veri türüdür, ancak bunun dinamik doğası nedeniyle bellek kullanımı ve performans açısından belirli özelliklere sahiptir. Örneğin, bir haritanın boyutu dinamik olarak büyüyebilir ve depolanan elemanların sayısı mevcut kapasiteyi aştığında, harita büyüyen talebi karşılamak için otomatik olarak daha büyük bir depolama alanı yeniden ayırır.

Bu dinamik büyüme, özellikle büyük haritalarla veya performansı hassas olan uygulamalarda performans sorunlarına neden olabilir. Performansı optimize etmek için bir harita oluştururken makul bir başlangıç kapasitesi belirtebilirsiniz. Örneğin:

myMap := make(map[string]int, 100)

Bu, çalışma zamanındaki haritanın dinamik olarak genişlemesiyle ilgili fazladan maliyeti azaltabilir.

5.2 Haritaların Referans Türü Özellikleri

Haritalar referans türleri olduğundan, bir haritayı başka bir değişkene atadığınızda, yeni değişken orijinal harita ile aynı veri yapısına referans yapacaktır. Bu ayrıca, yeni değişken aracılığıyla haritada değişiklik yaparsanız, bu değişiklikler orijinal harita de de yansıyacaktır.

İşte bir örnek:

package main

import "fmt"

func main() {
    originalMap := map[string]int{"Alice": 23, "Bob": 25}
    newMap := originalMap

    newMap["Charlie"] = 28

    fmt.Println(originalMap) // Çıktı, yeni eklenen "Charlie": 28 anahtar-değer çiftini gösterecektir
}

Bir fonksiyon çağrısında bir haritayı parametre olarak geçirirken, referans türü davranışını akılda tutmak da önemlidir. Bu noktada, geçilen şey bir kopya değil, haritaya referanstır.

5.3 Eş Zamanlılık Güvenliği ve sync.Map

Bir haritayı çoklu iş parçacıklı bir ortamda kullanırken, doğru senkronizasyon uygulanmazsa eş zamanlılık güvenliği konularına özel dikkat edilmesi gerekir. Go standart kütüphanesi, çoklu ortamlar için tasarlanmış güvenli bir harita olan sync.Map türünü sağlar. Bu tip, harita üzerinde işlem yapmak için Load, Store, LoadOrStore, Delete ve Range gibi temel yöntemler sunar.

Aşağıda sync.Map kullanımına dair bir örnek bulunmaktadır:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mySyncMap sync.Map

    // Anahtar-değer çiftlerini saklama
    mySyncMap.Store("Alice", 23)
    mySyncMap.Store("Bob", 25)

    // Bir anahtar-değer çiftini alıp yazdırma
    if value, ok := mySyncMap.Load("Alice"); ok {
        fmt.Printf("Anahtar: Alice, Değer: %d\n", value)
    }

    // Range yöntemini kullanarak sync.Map üzerinde dolaşma
    mySyncMap.Range(func(key, value interface{}) bool {
        fmt.Printf("Anahtar: %v, Değer: %v\n", key, value)
        return true // döngüye devam et
    })
}

sync.Map'i, çoklu ortamda haritayı değiştirirken yarış koşulu sorunlarını önlemek için kullanarak, iplik güvenliğini sağlayabilirsiniz.