Membuat dan memakai map
Tipe map ditulis map[KeyType]ValueType. Buat dengan literal atau dengan make, lalu baca, tulis, dan hapus berdasarkan key.
Output:
31
3
map[bob:26 cy:40]
2 0
Ada dua kemudahan di sini. Membaca key yang tidak ada mengembalikan zero value dari tipe nilainya (counts['z'] bernilai 0), sehingga menghitung dengan m[k]++ langsung bisa tanpa persiapan apa pun. Dan fmt mencetak map dengan key yang terurut, yang berguna untuk debugging tapi tidak mengatakan apa pun tentang urutan iterasi.
make(map[K]V, n) menerima petunjuk ukuran opsional. Ia menyiapkan tempat untuk sekitar n entri; berbeda dengan slice, map tidak punya kapasitas yang bisa kamu baca kembali.
Memeriksa apakah key ada: comma-ok
Karena key yang tidak ada terbaca sebagai zero value, m[k] == 0 tidak bisa membedakan "tidak ada" dari "disimpan sebagai 0". Pakai bentuk dua nilai:
Bentuk if v, ok := m[k]; ok { ... } membuat v dan ok hanya berlaku di dalam if. Ini salah satu baris yang paling sering muncul di kode Go.
Menghapus entri
delete(m, key) menghapus entri. Menghapus key yang tidak ada tidak melakukan apa-apa, begitu juga menghapus dari map nil. Untuk mengosongkan seluruh map, Go 1.21 menambahkan clear(m), yang mempertahankan map yang sudah dialokasikan supaya bisa dipakai lagi.
Menghapus entri selama range pada map yang sama diperbolehkan dan aman. Entri yang dihapus sebelum perulangan sampai di sana tidak akan dihasilkan.
Iterasi: urutannya acak
for k, v := range m mengunjungi setiap entri satu kali, dalam urutan yang tidak ditentukan. Runtime sengaja mengacak titik awalnya, jadi dua perulangan pada map yang sama di program yang sama sering berbeda hasilnya. Jalankan ini beberapa kali:
Kode apa pun yang output-nya bergantung pada urutan map adalah bug yang tinggal menunggu run yang berbeda. Pengujian yang membandingkan hasil cetak iterasi map adalah contoh klasiknya.
Key yang terurut
Untuk mengunjungi map berdasarkan urutan key, ambil key-nya, urutkan, lalu akses map dengan key tersebut. Go 1.23 membuat ini cukup satu baris dengan iterator dari package maps dan slices:
Di Go 1.22 dan sebelumnya, maps.Keys belum ada di standard library. Padanannya adalah perulangan:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
Fungsi bantu lain di maps: maps.Values, maps.Clone (salinan dangkal), maps.Equal, maps.Copy(dst, src), dan maps.DeleteFunc.
Tipe key yang valid
Key harus bisa dibandingkan dengan ==: angka, string, boolean, pointer, channel, array dari tipe yang bisa dibandingkan, struct yang semua field-nya bisa dibandingkan, dan nilai interface. Slice, map, dan fungsi tidak bisa menjadi key.
map[[]int]bool{} // compile error: invalid map key type []int
map[[2]int]bool{} // fine: arrays are comparable
map[struct{ X, Y int }]string{} // fine: a struct key for a grid position
Key berupa struct adalah cara idiomatis untuk memakai beberapa nilai sekaligus sebagai key, alih-alih menyambung string.
Key berupa interface tetap bisa dikompilasi walaupun tipe dinamisnya tidak bisa dibandingkan, lalu panic saat program berjalan: menyimpan []int di map[any]int gagal dengan runtime error: hash of unhashable type []int.
Key floating-point bisa dipakai, tapi NaN tidak sama dengan dirinya sendiri, jadi key NaN bisa dimasukkan berulang kali dan tidak pernah bisa dibaca kembali. Hindari key float.
Map of structs
Map bisa menyimpan struct, tapi kamu tidak bisa meng-assign field dari struct yang tersimpan di map, karena nilai map tidak addressable.
Pilih nilai biasa saat entrinya kecil dan selalu diganti secara utuh. Pilih pointer saat kamu sering memperbarui field atau berbagi record yang sama dari beberapa tempat. Dengan pointer, key yang tidak ada mengembalikan nil, jadi ptrs["nope"].Score akan panic.
Map of slices bekerja dengan cara yang sama untuk append: groups[k] = append(groups[k], v) tidak butuh inisialisasi, karena key yang tidak ada memberikan slice nil dan append bisa menangani nil.
Map berperilaku seperti referensi
Nilai map merujuk ke data yang dipakai bersama. Meng-assign map atau mengirimkannya ke fungsi tidak menyalin entrinya: kedua variabel melihat map yang sama.
Itulah sebabnya sebuah fungsi bisa mengisi map tanpa mengembalikannya, berbeda dengan slice yang di-append.
Panic karena map nil
Zero value sebuah map adalah nil. Map nil terbaca seperti map kosong, tapi menulis ke dalamnya memicu panic.
Output:
0 0
recovered: assignment to entry in nil map
Kasus struct inilah yang sering menjebak dalam praktik. Inisialisasi field map di constructor (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) atau secara lazy sebelum penulisan pertama.
Akses concurrent
Map tidak aman dipakai secara concurrent. Kalau satu goroutine menulis sementara goroutine lain membaca atau menulis, runtime bisa menghentikan program dengan fatal error: concurrent map writes (atau concurrent map read and map write). Ini fatal error, bukan panic, jadi recover tidak bisa menangkapnya.
Lindungi map dengan mutex:
Program ini selalu mencetak 50 50. Pakai sync.RWMutex saat jumlah pembacaan jauh melebihi penulisan. sync.Map ada untuk dua kasus sempit (key yang ditulis sekali lalu dibaca berkali-kali, atau goroutine yang bekerja pada key yang terpisah); untuk kasus lainnya, mutex dan map biasa lebih sederhana dan biasanya lebih cepat. Selengkapnya di mutex.
Ringkasan cepat
| Operasi | Kode |
|---|---|
| Membuat | m := map[string]int{} atau make(map[string]int) |
| Menambah atau memperbarui | m[k] = v |
| Membaca (nol kalau tidak ada) | v := m[k] |
| Memeriksa keberadaan | v, ok := m[k] |
| Menghapus | delete(m, k) |
| Menghapus semua | clear(m) (Go 1.21) |
| Ukuran | len(m) |
| Key terurut | slices.Sorted(maps.Keys(m)) (Go 1.23) |
| Menyalin | maps.Clone(m) |
| Membandingkan | maps.Equal(a, b) |
Map dengan nilai struct{} juga merupakan tipe set di Go; lihat set.
Kesalahan umum
- Menulis ke map nil. Selalu buat dengan
make, termasuk field map di dalam struct. - Mengandalkan urutan iterasi. Urutkan key-nya.
- Memakai
m[k] != 0untuk memeriksa keberadaan. Pakai comma-ok. - Mengubah field struct lewat
m[k].Field. Salin keluar lalu tulis kembali, atau simpan pointer. - Berbagi map antar goroutine tanpa lock. Crash-nya tidak bisa di-recover.
Pertanyaan yang sering diajukan
Bagaimana cara memeriksa apakah sebuah key ada di map Go?
Pakai bentuk dua nilai saat mengakses: v, ok := m[key]. ok bernilai true kalau key-nya ada dan false kalau tidak, dan dalam kasus itu v berisi zero value. Membaca m[key] saja tidak bisa membedakan key yang tidak ada dari key yang disimpan dengan zero value.
Kenapa urutan iterasi map di Go acak?
Bahasa Go tidak menetapkan urutannya, dan runtime sengaja memulai setiap range dari posisi acak supaya program tidak bergantung pada satu urutan tertentu. Untuk melakukan iterasi berdasarkan urutan key, kumpulkan lalu urutkan key-nya: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).
Bagaimana cara mendapatkan semua key dari map di Go?
Sejak Go 1.23, maps.Keys(m) mengembalikan iterator; ubah menjadi slice dengan slices.Collect(maps.Keys(m)), atau slice yang terurut dengan slices.Sorted(maps.Keys(m)). Sebelum 1.23, lakukan perulangan dengan for k := range m lalu tambahkan setiap key ke slice dengan append.
Kenapa menulis ke map memicu panic "assignment to entry in nil map"?
Variabel map sudah dideklarasikan tapi belum pernah dibuat: var m map[string]int bernilai nil. Membaca map nil mengembalikan zero value, tapi menulis ke dalamnya memicu panic. Buat dulu dengan m = make(map[string]int) atau literal m := map[string]int{}. Field map di dalam struct juga butuh inisialisasi yang sama.
Apakah map Go aman dipakai secara concurrent?
Tidak. Penulisan concurrent, atau penulisan bersamaan dengan pembacaan, bisa membuat program crash dengan fatal error: concurrent map writes, yang tidak bisa ditangkap recover. Lindungi map dengan sync.Mutex atau sync.RWMutex, atau pakai sync.Map untuk kasus khusus yang memang menjadi tujuannya.