Menu

Golang Map: erstellen, Schlüssel prüfen, durchlaufen, sortieren

Go-Maps speichern Schlüssel-Wert-Paare mit schnellem Lookup. So erzeugst du sie, prüfst mit Comma-ok, ob ein Schlüssel existiert, löschst, durchläufst sie (in zufälliger Reihenfolge), sortierst Schlüssel, speicherst Structs und vermeidest die Panics bei nil-Maps und nebenläufigem Schreiben.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Eine Map erzeugen und nutzen

Ein Map-Typ wird map[KeyType]ValueType geschrieben. Du erzeugst eine mit einem Literal oder mit make und liest, schreibst und löschst dann per Schlüssel.

Ausgabe:

31
3
map[bob:26 cy:40]
2 0

Zwei Erleichterungen zeigen sich hier. Das Lesen eines fehlenden Schlüssels liefert den Nullwert des Werttyps (counts['z'] ist 0), also funktioniert Zählen mit m[k]++ ohne jede Vorbereitung. Und fmt gibt Maps mit sortierten Schlüsseln aus. Das ist praktisch beim Debuggen, sagt aber nichts über die Iterationsreihenfolge.

make(map[K]V, n) nimmt einen optionalen Größenhinweis. Es reserviert Platz für etwa n Einträge; anders als bei Slices hat eine Map keine Kapazität, die du auslesen kannst.

Prüfen, ob ein Schlüssel existiert: Comma-ok

Weil ein fehlender Schlüssel als Nullwert gelesen wird, kann m[k] == 0 „fehlt“ nicht von „als 0 gespeichert“ unterscheiden. Nimm die Form mit zwei Werten:

Die Form if v, ok := m[k]; ok { ... } hält v und ok im Scope des if. Sie ist eine der häufigsten Zeilen in Go-Code.

Einträge löschen

delete(m, key) entfernt den Eintrag. Einen Schlüssel zu löschen, der nicht vorhanden ist, bewirkt nichts, ebenso das Löschen aus einer nil-Map. Um eine ganze Map zu leeren, hat Go 1.21 clear(m) eingeführt, das die angelegte Map zur Wiederverwendung behält.

Einträge während eines range über dieselbe Map zu löschen ist erlaubt und sicher. Ein Eintrag, der gelöscht wird, bevor die Schleife ihn erreicht, wird nicht geliefert.

Durchlaufen: Die Reihenfolge ist zufällig

for k, v := range m besucht jeden Eintrag einmal, in einer nicht festgelegten Reihenfolge. Die Runtime randomisiert den Startpunkt absichtlich, also sind sich zwei Schleifen über dieselbe Map im selben Programm oft uneinig. Führ das ein paarmal aus:

Jeder Code, dessen Ausgabe von der Reihenfolge einer Map abhängt, ist ein Bug, der auf einen anderen Lauf wartet. Tests, die eine ausgegebene Map-Iteration vergleichen, sind das klassische Beispiel.

Sortierte Schlüssel

Um eine Map in Schlüsselreihenfolge zu besuchen, holst du die Schlüssel, sortierst sie und indizierst die Map. Go 1.23 hat das mit Iteratoren aus den Paketen maps und slices zu einer Zeile gemacht:

Unter Go 1.22 und älter gab es maps.Keys in der Standardbibliothek nicht. Das Gegenstück ist eine Schleife:

keys := make([]string, 0, len(m))
for k := range m {
	keys = append(keys, k)
}
sort.Strings(keys)

Weitere Helfer in maps: maps.Values, maps.Clone (eine flache Kopie), maps.Equal, maps.Copy(dst, src) und maps.DeleteFunc.

Gültige Schlüsseltypen

Schlüssel müssen mit == vergleichbar sein: Zahlen, Strings, Booleans, Pointer, Channels, Arrays aus vergleichbaren Typen, Structs, deren Felder alle vergleichbar sind, und Interface-Werte. Slices, Maps und Funktionen können keine Schlüssel sein.

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

Ein Struct als Schlüssel ist der idiomatische Weg, nach mehreren Werten gleichzeitig zu indizieren, statt Strings zu verketten.

Interface-Schlüssel kompilieren auch dann, wenn der dynamische Typ nicht vergleichbar ist, und lösen dann zur Laufzeit eine Panic aus: Einen []int unter einer map[any]int zu speichern scheitert mit runtime error: hash of unhashable type []int.

Gleitkomma-Schlüssel funktionieren, aber NaN ist nicht gleich sich selbst, also lässt sich ein NaN-Schlüssel wiederholt einfügen und nie wieder lesen. Vermeide Float-Schlüssel.

Map von Structs

Eine Map kann Structs enthalten, aber du kannst keinem Feld eines Structs zuweisen, das in einer Map gespeichert ist, weil Map-Werte nicht adressierbar sind.

Nimm Werte, wenn die Einträge klein sind und als Ganzes ersetzt werden. Nimm Pointer, wenn du Felder oft aktualisierst oder denselben Datensatz von mehreren Stellen aus teilst. Mit Pointern liefert ein fehlender Schlüssel nil, also löst ptrs["nope"].Score eine Panic aus.

Maps von Slices funktionieren beim Anhängen genauso: groups[k] = append(groups[k], v) braucht keine Initialisierung, weil ein fehlender Schlüssel einen nil-Slice liefert und append mit nil umgehen kann.

Maps verhalten sich wie Referenzen

Ein Map-Wert verweist auf geteilte Daten. Eine Map zuzuweisen oder an eine Funktion zu übergeben kopiert die Einträge nicht: Beide Variablen sehen dieselbe Map.

Deshalb kann eine Funktion eine Map füllen, ohne sie zurückzugeben, anders als bei einem Slice, an den sie anhängt.

Die Panic bei der nil-Map

Der Nullwert einer Map ist nil. Eine nil-Map liest sich wie eine leere Map, aber das Schreiben darauf löst eine Panic aus.

Ausgabe:

0 0
recovered: assignment to entry in nil map

Der Fall mit dem Struct ist der, der in der Praxis zubeißt. Initialisier Map-Felder in einem Konstruktor (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) oder verzögert vor dem ersten Schreiben.

Nebenläufiger Zugriff

Maps sind nicht sicher für nebenläufige Nutzung. Schreibt eine Goroutine, während eine andere liest oder schreibt, kann die Runtime das Programm mit fatal error: concurrent map writes (oder concurrent map read and map write) stoppen. Das ist ein fataler Fehler, keine Panic, also kann recover ihn nicht abfangen.

Schütze die Map mit einem Mutex:

Das gibt immer 50 50 aus. Nimm sync.RWMutex, wenn Lesezugriffe die Schreibzugriffe weit überwiegen. sync.Map gibt es für zwei enge Fälle (Schlüssel, die einmal geschrieben und oft gelesen werden, oder Goroutinen, die mit getrennten Schlüsseln arbeiten); für alles andere ist ein Mutex mit einer normalen Map einfacher und meist schneller. Mehr dazu unter Mutex.

Kurzreferenz

OperationCode
Erzeugenm := map[string]int{} oder make(map[string]int)
Einfügen oder aktualisierenm[k] = v
Lesen (null, wenn fehlend)v := m[k]
Vorhandensein prüfenv, ok := m[k]
Löschendelete(m, k)
Alles entfernenclear(m) (Go 1.21)
Größelen(m)
Sortierte Schlüsselslices.Sorted(maps.Keys(m)) (Go 1.23)
Kopierenmaps.Clone(m)
Vergleichenmaps.Equal(a, b)

Eine Map mit struct{}-Werten ist außerdem der Set-Typ von Go; siehe Sets.

Häufige Fehler

  • In eine nil-Map schreiben. Immer mit make anlegen, auch Map-Felder in Structs.
  • Sich auf die Iterationsreihenfolge verlassen. Sortier die Schlüssel.
  • m[k] != 0 als Test auf Vorhandensein nutzen. Nimm Comma-ok.
  • Ein Struct-Feld über m[k].Field ändern. Herauskopieren und zurückschreiben oder Pointer speichern.
  • Eine Map ohne Lock zwischen Goroutinen teilen. Der Absturz lässt sich nicht abfangen.

Häufig gestellte Fragen

Wie prüft man, ob ein Schlüssel in einer Go-Map existiert?

Mit der Form des Lookups mit zwei Werten: v, ok := m[key]. ok ist true, wenn der Schlüssel vorhanden ist, und false, wenn nicht; dann ist v der Nullwert. Ein einfaches m[key] kann einen fehlenden Schlüssel nicht von einem Schlüssel unterscheiden, der mit dem Nullwert gespeichert ist.

Warum ist die Iterationsreihenfolge einer Go-Map zufällig?

Die Sprache legt keine Reihenfolge fest, und die Runtime startet jedes range absichtlich an einer zufälligen Position, damit Programme nicht von einer Reihenfolge abhängig werden. Um in Schlüsselreihenfolge zu iterieren, sammle und sortiere die Schlüssel: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).

Wie bekomme ich in Go alle Schlüssel einer Map?

Seit Go 1.23 gibt maps.Keys(m) einen Iterator zurück; mach daraus mit slices.Collect(maps.Keys(m)) einen Slice oder mit slices.Sorted(maps.Keys(m)) einen sortierten Slice. Vor 1.23 schreibst du eine Schleife for k := range m und hängst jeden Schlüssel an einen Slice an.

Warum löst das Schreiben in eine Map die Panic „assignment to entry in nil map“ aus?

Die Map-Variable wurde deklariert, aber nie erzeugt: var m map[string]int ist nil. Aus einer nil-Map zu lesen liefert Nullwerte, aber Schreiben löst eine Panic aus. Erzeuge sie vorher mit m = make(map[string]int) oder einem Literal m := map[string]int{}. Ein Map-Feld in einem Struct braucht dieselbe Initialisierung.

Sind Go-Maps sicher für nebenläufige Nutzung?

Nein. Nebenläufiges Schreiben, oder Schreiben parallel zum Lesen, kann das Programm mit fatal error: concurrent map writes abstürzen lassen, und recover kann das nicht abfangen. Schütze die Map mit einem sync.Mutex oder sync.RWMutex oder nimm sync.Map für die speziellen Fälle, für die es gedacht ist.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S