Go dilinde map, anahtarları benzersiz olan ve bir anahtar tipini bir değer tipine eşleyen yerleşik bir hash tablosudur. map[string]int{"a": 1} literaliyle ya da make(map[string]int) ile oluşturulur, m[k] = v ile yazılır, v, ok := m[k] ile anahtarın varlığı kontrol edilerek okunur ve delete(m, k) ile silinir. Anahtar tipi == ile karşılaştırılabilir olmalıdır; gezinme sırası belirsizdir ve nil bir map okunabilir ama yazılamaz.

Bu yazıda map tanımlamayı, "comma ok" deyimini, sıfır değerlerden yararlanmayı, struct anahtarları, sıralı gezinmeyi, clear ve maps paketini, map değerlerinin referans gibi davranmasını ve en sık görülen hataları Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Serinin diğer yazıları için Go öğrenme rehberi sayfasına bakabilirsiniz.

Map tanımlama ve temel işlemler

Map tanımlama ve temel işlemler

Map tipi map[AnahtarTipi]DegerTipi biçiminde yazılır. Değer tipi herhangi bir tip olabilir, başka bir map dahil. Spesifikasyona göre başlatılmamış bir map değişkeninin değeri nildir. Kullanıma hazır bir map elde etmenin iki yolu vardır: make(map[K]V) çağrısı ya da bir map literali. Go Blog, boş literal map[string]int{} ile make çağrısının işlevsel olarak aynı olduğunu belirtir.

go
package main

import "fmt"

func main() {
	stok := map[string]int{
		"kalem":  12,
		"defter": 0,
	}
	stok["silgi"] = 5

	fmt.Println(stok["kalem"], stok["cetvel"]) // olmayan anahtar: 0

	adet, ok := stok["defter"]
	fmt.Println(adet, ok)
	adet, ok = stok["cetvel"]
	fmt.Println(adet, ok)

	if _, var_mi := stok["silgi"]; var_mi {
		fmt.Println("silgi kayıtlı")
	}

	delete(stok, "kalem")
	delete(stok, "olmayan") // hata vermez
	fmt.Println(len(stok), stok)
}

// Çıktı:
// 12 0
// 0 true
// 0 false
// silgi kayıtlı
// 2 map[defter:0 silgi:5]

Bu programda birkaç önemli davranış var. Olmayan bir anahtarı okumak hata vermez, değer tipinin sıfır değerini döndürür: stok["cetvel"] sonucu 0 olur. Bu yüzden 0 değerinin "kayıtlı ama sıfır" mı yoksa "hiç yok" mu olduğunu ayırmak için iki değerli atama gerekir. adet, ok := stok["defter"] ifadesinde ok, anahtar map içinde varsa true olur. Effective Go bu kalıba "comma ok" deyimi der. Yalnızca varlığı sınamak istiyorsanız değer yerine boş tanımlayıcı _ kullanabilirsiniz.

delete yerleşik fonksiyonu bir şey döndürmez ve anahtar yoksa hiçbir şey yapmaz; spesifikasyona göre nil map üzerinde çağrıldığında da etkisizdir. len(m) tanımlı anahtar sayısını verir. Map için cap fonksiyonu tanımlı değildir. make çağrısına ikinci argüman olarak bir kapasite ipucu verebilirsiniz (make(map[string]int, 100)); spesifikasyon bunun yaklaşık bu kadar eleman için başlangıç alanı ayırdığını, ancak boyutu sınırlamadığını ve kesin davranışın uygulamaya bağlı olduğunu söyler.

nil map: okunur ama yazılamaz

Spesifikasyon nil map'i "eleman eklenemeyen boş bir map" olarak tanımlar. Okuma, len, range ve delete nil map üzerinde sorunsuz çalışır; atama ise çalışma anında paniğe neden olur:

go
package main

import "fmt"

func main() {
	var m map[string]int // nil map
	fmt.Println(m == nil, len(m), m["x"])
	for k := range m {
		fmt.Println(k) // hiç çalışmaz
	}
	delete(m, "x") // nil map'te etkisiz
	m["x"] = 1     // panic
}
text
true 0 0
panic: assignment to entry in nil map

Bu hata çoğunlukla bir struct içindeki map alanının başlatılmadığı durumlarda görülür: var c Config sonrasında c.Etiketler["x"] = "y" yazmak panikler, çünkü alanın sıfır değeri nil'dir. Çözüm, map'i bir kurucu fonksiyonda ya da ilk kullanımdan önce make ile başlatmaktır. Yalnızca okuma yapan kodlarda nil map'i boş map gibi kullanmak güvenlidir; fonksiyonlarınız "kayıt yok" anlamında nil map döndürebilir.

Hangi tipler anahtar olabilir?

Spesifikasyona göre anahtar tipinde == ve != operatörleri tam olarak tanımlı olmalıdır; bu yüzden anahtar tipi fonksiyon, map veya dilim olamaz. Bool, sayısal tipler, string, işaretçi, kanal ve arayüz tipleri ile yalnızca bu tiplerden oluşan struct ve diziler anahtar olabilir. Dilim anahtarlı bir map tanımlamaya çalışmak derleme hatası verir:

go
package main

func main() {
	m := map[[]int]string{}
	_ = m
}
text
# command-line-arguments
./main.go:4:11: invalid map key type []int

Anahtar tipi bir arayüz olduğunda kontrol çalışma zamanına kalır: arayüzün içindeki dinamik değer karşılaştırılamayan bir tipse (ör. bir dilim) işlem panikler. Go SSS sayfası dilimlerin anahtar olamamasını, dilimler için eşitliğin iyi tanımlanmamış olmasıyla açıklar; struct ve diziler için eşitlik tanımlı olduğundan bunlar anahtar olarak kullanılabilir.

Sıfır değerlerden yararlanmak ve struct anahtarlar

Olmayan anahtarın sıfır değer döndürmesi birçok kalıbı kısaltır. Bir sayaç için m[k]++ yazmak yeterlidir, çünkü ilk artışta okunan değer 0'dır. Değeri dilim olan bir map'te m[k] = append(m[k], v) anahtar yoksa nil dilime ekleme yapar ve bu geçerlidir. Go Blog ayrıca iç içe map yerine struct anahtar kullanmanın ekleme kodunu sadeleştirdiğini gösterir:

go
package main

import "fmt"

type Anahtar struct {
	Yol, Ulke string
}

func main() {
	ziyaret := make(map[Anahtar]int)
	ziyaret[Anahtar{"/", "tr"}]++
	ziyaret[Anahtar{"/", "tr"}]++
	ziyaret[Anahtar{"/blog", "de"}]++
	fmt.Println(ziyaret[Anahtar{"/", "tr"}], len(ziyaret))

	// Değeri dilim olan map: nil dilime append doğrudan çalışır.
	etiket := map[string][]string{}
	etiket["go"] = append(etiket["go"], "slice")
	etiket["go"] = append(etiket["go"], "map")
	fmt.Println(etiket["go"], len(etiket["python"]))

	// Küme: struct{} tipinin boyutu sıfırdır.
	gorulen := map[string]struct{}{}
	for _, k := range []string{"a", "b", "a"} {
		gorulen[k] = struct{}{}
	}
	_, var_mi := gorulen["a"]
	fmt.Println(len(gorulen), var_mi)
}

// Çıktı:
// 2 2
// [slice map] 0
// 2 true

map[string]map[string]int gibi iç içe bir yapıda her eklemeden önce iç map'in var olup olmadığını kontrol etmeniz gerekirdi. Anahtar struct'ı ise iki boyutu tek bir karşılaştırılabilir değerde topladığı için ziyaret[Anahtar{"/", "tr"}]++ tek satırda hem oluşturur hem artırır. Örneğin son kısmında küme (set) için map[string]struct{} kullanıldı; spesifikasyona göre alanı olmayan bir struct tipinin boyutu sıfırdır. Effective Go küme için map[string]bool kullanımını da gösterir; bu durumda if kume[x] yazımı daha okunaklıdır.

Gezinme sırası ve sıralı çıktı

Spesifikasyon map üzerinde range sırasının belirtilmediğini ve bir gezinmeden diğerine aynı kalmasının garanti edilmediğini açıkça söyler. Sıralı bir çıktıya ihtiyacınız varsa anahtarları ayrı bir dilimde toplayıp sıralamanız gerekir. Go 1.23 ve sonrasında bunu maps.Keys yineleyicisi ile slices.Sorted fonksiyonunu birleştirerek tek satırda yapabilirsiniz:

go
package main

import (
	"fmt"
	"maps"
	"slices"
)

func main() {
	nufus := map[string]int{
		"İzmir":   3,
		"Ankara":  5,
		"Bursa":   2,
		"Antalya": 2,
	}

	// Sıralı gezinme: önce anahtarları toplayıp sıralayın.
	for _, il := range slices.Sorted(maps.Keys(nufus)) {
		fmt.Println(il, nufus[il])
	}

	// fmt paketi map'leri anahtara göre sıralı yazdırır.
	fmt.Println(nufus)
}

// Çıktı:
// Ankara 5
// Antalya 2
// Bursa 2
// İzmir 3
// map[Ankara:5 Antalya:2 Bursa:2 İzmir:3]

Burada iki ayrıntıya dikkat edin. Birincisi, fmt paketi map'leri anahtara göre sıralı yazdırır; bu yalnızca çıktı biçimidir ve range sırası hakkında hiçbir şey söylemez. İkincisi, string sıralaması bayt bazındadır: "İzmir" ilk baytı ASCII dışında olduğu için Latin harfleriyle başlayan illerin arkasına düşer. Türkçe alfabe sırasına göre sıralama standart kütüphanenin sort veya slices paketlerinin varsayılan davranışı değildir; string karşılaştırması ve Türkçe karakterler için Go string, rune ve byte yazısına bakabilirsiniz.

Gezinme sırasında map'i değiştirmek de spesifikasyonda tanımlanmıştır: henüz ulaşılmamış bir giriş silinirse o giriş üretilmez; gezinme sırasında eklenen bir giriş ise üretilebilir de atlanabilir de. Yani döngü içinde silmek güvenlidir ve öngörülebilirdir:

go
package main

import "fmt"

func main() {
	puan := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4}
	for k, v := range puan {
		if v%2 == 0 {
			delete(puan, k) // gezinirken silmek güvenlidir
		}
	}
	fmt.Println(puan)
}

// Çıktı:
// map[a:1 c:3]

Map bir referans gibi davranır

Map bir referans gibi davranır: Okuma, Varlık kontrolü, Yazma, Silme

Go SSS sayfasına göre map ve dilim değerleri işaretçi gibi davranır: alttaki veriyi gösteren tanımlayıcılardır ve kopyalandıklarında gösterdikleri veri kopyalanmaz. Bir map'i başka bir değişkene atamak ya da fonksiyona vermek aynı hash tablosunu paylaşır; fonksiyon içinde yapılan ekleme çağıranda görünür. Bağımsız bir kopya için maps.Clone kullanılır:

go
package main

import (
	"fmt"
	"maps"
)

func ekle(m map[string]int) {
	m["yeni"] = 1 // çağıranın map'i değişir
}

func main() {
	a := map[string]int{"x": 1}
	b := a // aynı map'i gösterir
	b["y"] = 2
	ekle(a)
	fmt.Println(len(a), a)

	c := maps.Clone(a) // bağımsız (sığ) kopya
	c["x"] = 100
	fmt.Println(a["x"], c["x"], maps.Equal(a, c))

	clear(a)
	fmt.Println(len(a), len(b), a == nil)
}

// Çıktı:
// 3 map[x:1 y:2 yeni:1]
// 1 100 false
// 0 0 false

maps.Clone belgesine göre kopya sığdır: anahtar ve değerler sıradan atamayla kopyalanır. Değerler dilim ya da işaretçi ise kopya ile orijinal hâlâ aynı alttaki veriyi paylaşır. maps.Equal iki map'in aynı anahtar ve değer çiftlerini içerip içermediğini raporlar. Go 1.21 sürüm notlarında duyurulan yerleşik clear fonksiyonu map'teki tüm girişleri siler; spesifikasyona göre sonuç boş bir map'tir (len(m) == 0), map nil olmaz. Aynı map'i paylaşan b değişkeninin de boşaldığına dikkat edin. clear nil map üzerinde çağrılırsa hiçbir şey yapmaz.

İşlemYazımnil map üzerinde
Okumav := m[k]Sıfır değer döner
Varlık kontrolüv, ok := m[k]ok false olur
Yazmam[k] = vPanik
Silmedelete(m, k)Etkisiz
Tümünü silmeclear(m)Etkisiz
Uzunluklen(m)0
Gezinmefor k, v := range mHiç dönmez
Sıralı anahtarlarslices.Sorted(maps.Keys(m))Boş dilim (nil)
Kopyamaps.Clone(m)nil döner

Tablodaki son iki satırın nil map davranışını belgeler doğrudan yazmıyor; bunları Go 1.27.3 ile çalıştırarak doğruladık (maps.Clone nil map için nil döndürdü, sıralı anahtar dilimi de nil çıktı). Go 1.23 sürüm notları, maps paketine yineleyicilerle çalışan fonksiyonların eklendiğini duyurur. maps paketindeki All, Keys, Values, Collect, Insert ve DeleteFunc fonksiyonları yineleyicilerle çalışır; bunların ayrıntıları slices, maps ve sort paketleri ve Go iterator (range over func) yazılarında.

Struct değerli map'te alan güncellemek

Map elemanları adreslenebilir değildir: &m[k] yazamazsınız ve değeri struct olan bir map'te bir alanı doğrudan değiştiremezsiniz. Spesifikasyona göre adres operatörünün operandı değişken, işaretçi dolaylaması, dilim indeksleme ya da adreslenebilir bir struct'ın alanı olmalıdır; map indeksleme bu listede yoktur. Derleyici şu hatayı verir:

go
package main

type Urun struct{ Fiyat int }

func main() {
	m := map[string]Urun{"kalem": {10}}
	m["kalem"].Fiyat = 12
}
text
# command-line-arguments
./main.go:7:2: cannot assign to struct field m["kalem"].Fiyat in map

İki çözüm vardır: değeri bir değişkene okuyup değiştirmek ve map'e geri yazmak, ya da map'in değer tipini işaretçi yapmak. İşaretçi kullandığınızda alan değişikliği doğrudan görünür, ama nil işaretçi kontrolünü ve paylaşılan verinin sonuçlarını sizin yönetmeniz gerekir:

go
package main

import "fmt"

type Urun struct{ Fiyat int }

func main() {
	m := map[string]Urun{"kalem": {10}}
	u := m["kalem"]
	u.Fiyat = 12
	m["kalem"] = u // değeri geri yazın

	p := map[string]*Urun{"defter": {20}}
	p["defter"].Fiyat = 25 // işaretçi değerde alan değişir

	fmt.Println(m["kalem"].Fiyat, p["defter"].Fiyat)
}

// Çıktı:
// 12 25

Eşzamanlı erişim

Go Blog ve Go SSS, map'lerin eşzamanlı kullanım için güvenli olmadığını belirtir: aynı map'e farklı goroutine'lerden aynı anda yazıp okumanın sonucu tanımlı değildir. SSS'e göre tüm goroutine'ler yalnızca okuma yapıyorsa (gezinme dahil) eşzamanlı erişim senkronizasyon olmadan güvenlidir; sorun güncelleme olduğunda başlar. Bazı uygulamalar güvensiz eşzamanlı değişikliği çalışma anında otomatik olarak raporlayan bir kontrol içerir. Yazma varsa erişimi sync.Mutex ya da sync.RWMutex ile korumanız gerekir; belirli kullanım kalıpları için sync.Map tipi de vardır. Kilitler ve sync.Map için Go sync: Mutex ve WaitGroup, veri yarışı kavramı için Go sync/atomic ve veri yarışı yazısına bakın.

Sık yapılan hatalar

Sık yapılan hatalar: nil map'e yazmak, Sıfır değeri "yok" sanmak, Gezinme sırasına güvenmek, Struct alanını map üzerinden değiştirmek
  • nil map'e yazmak: var m map[string]int sonrası m["a"] = 1 panikler. Önce make ile başlatın.
  • Sıfır değeri "yok" sanmak: m[k] == 0 anahtarın olmadığını kanıtlamaz; v, ok := m[k] kullanın.
  • Gezinme sırasına güvenmek: range sırası belirsizdir. Test çıktısı ya da kullanıcıya gösterilen liste için anahtarları sıralayın.
  • Struct alanını map üzerinden değiştirmek: m[k].Alan = v derlenmez; değeri okuyup geri yazın ya da işaretçi değer kullanın.
  • Kopyalandığını sanmak: b := a aynı map'i paylaşır. Bağımsız kopya için maps.Clone kullanın.
  • Senkronizasyonsuz eşzamanlı yazma: goroutine'ler arasında paylaşılan map'i kilitle koruyun.
  • Dilim anahtar kullanmaya çalışmak: dilim karşılaştırılamaz; gerekiyorsa sabit uzunluklu dizi veya string anahtar kullanın.

Bu hataların derleme ve çalışma zamanı mesajlarının toplu listesi Go sık karşılaşılan hatalar yazısında.

Sık Sorulan Sorular

Go map'te bir anahtarın olup olmadığını nasıl anlarım?

İki değerli atama kullanın: v, ok := m[k]. ok true ise anahtar vardır. Yalnızca varlık için _, ok := m[k] yazabilirsiniz. Bu kontrolü if v, ok := m[k]; ok { ... } biçiminde kısa deyimli if içinde de yapabilirsiniz; ayrıntı için Go if ve switch yazısına bakın.

Map'i anahtara göre sıralı nasıl gezerim?

Map kendisi sıralanmaz. Anahtarları slices.Sorted(maps.Keys(m)) ile sıralı bir dilime alın ve o dilim üzerinde dönün. Eski sürümlerde anahtarları bir dilime append edip sort.Strings veya sort.Ints ile sıralamak aynı işi görür.

clear ile make arasındaki fark nedir?

clear(m) mevcut map'in girişlerini siler; aynı map'i paylaşan tüm değişkenler boşalmış map'i görür. m = make(map[K]V) ise yalnızca o değişkene yeni bir map atar; eski map'i tutan diğer değişkenler eski içeriği görmeye devam eder.

Map değeri fonksiyona verilince kopyalanır mı?

Map tanımlayıcısı kopyalanır ama gösterdiği hash tablosu paylaşılır. Fonksiyon içinde yapılan ekleme ve silmeler çağıranda görünür. Fonksiyonun map'i değiştirmesini istemiyorsanız maps.Clone ile kopya verin.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Go öğrenme rehberi · Go array ve slice farkı · Go struct yapıları · Go slices, maps ve sort paketleri · Go sync: Mutex ve WaitGroup · Go iterator (range over func)