Menu

Map in Golang: creare, controllare le chiavi, iterare, ordinare ed eliminare

Le map di Go memorizzano coppie chiave-valore con ricerca veloce. Impara a crearle, a controllare se una chiave esiste con comma-ok, a eliminare, iterare (in ordine casuale), ordinare le chiavi, memorizzare struct ed evitare i panic della map nil e delle scritture concorrenti.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Creare e usare una map

Un tipo map si scrive map[KeyType]ValueType. Creane una con un letterale o con make, poi leggi, scrivi ed elimina per chiave.

Output:

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

Qui si vedono due comodità. Leggere una chiave mancante restituisce lo zero value del tipo del valore (counts['z'] è 0), il che fa funzionare il conteggio con m[k]++ senza nessuna preparazione. E fmt stampa le map con le chiavi ordinate, utile per il debug ma senza dire nulla sull'ordine di iterazione.

make(map[K]V, n) accetta un'indicazione di dimensione facoltativa. Prealloca spazio per circa n elementi; a differenza delle slice, una map non ha una capacità che puoi rileggere.

Controllare se una chiave esiste: comma-ok

Siccome una chiave mancante si legge come zero value, m[k] == 0 non distingue "assente" da "memorizzato come 0". Usa la forma a due valori:

La forma if v, ok := m[k]; ok { ... } limita lo scope di v e ok all'if. È una delle righe più comuni nel codice Go.

Eliminare elementi

delete(m, key) rimuove l'elemento. Eliminare una chiave che non c'è non fa nulla, e lo stesso vale per l'eliminazione da una map nil. Per svuotare una map intera, Go 1.21 ha aggiunto clear(m), che mantiene la map allocata così si può riusare.

Eliminare elementi durante un range sulla stessa map è permesso e sicuro. Un elemento eliminato prima che il ciclo lo raggiunga non verrà prodotto.

Iterare: l'ordine è casuale

for k, v := range m visita ogni elemento una volta, in un ordine non specificato. Il runtime rende casuale di proposito il punto di partenza, quindi due cicli sulla stessa map nello stesso programma spesso non coincidono. Eseguilo qualche volta:

Qualsiasi codice il cui output dipende dall'ordine di una map è un bug che aspetta solo un'esecuzione diversa. I test che confrontano l'iterazione stampata di una map sono l'esempio classico.

Chiavi ordinate

Per visitare una map in ordine di chiave, prendi le chiavi, ordinale e indicizza la map. Go 1.23 l'ha reso una riga sola con gli iteratori dei package maps e slices:

Su Go 1.22 e precedenti, maps.Keys non esisteva nella libreria standard. L'equivalente è un ciclo:

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

Altre funzioni di supporto in maps: maps.Values, maps.Clone (una copia superficiale), maps.Equal, maps.Copy(dst, src) e maps.DeleteFunc.

Tipi di chiave validi

Le chiavi devono essere confrontabili con ==: numeri, stringhe, booleani, puntatori, channel, array di tipi confrontabili, struct i cui campi sono tutti confrontabili e valori interface. Slice, map e funzioni non possono essere chiavi.

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

Una chiave struct è il modo idiomatico per indicizzare con più valori insieme, invece di concatenare stringhe.

Le chiavi interface compilano anche quando il tipo dinamico non è confrontabile, e poi vanno in panic a runtime: memorizzare un []int in una map[any]int fallisce con runtime error: hash of unhashable type []int.

Le chiavi in virgola mobile funzionano, ma NaN non è uguale a se stesso, quindi una chiave NaN si può inserire più volte e non si riesce mai a rileggerla. Evita le chiavi float.

Map di struct

Una map può contenere struct, ma non puoi assegnare un valore al campo di una struct memorizzata in una map, perché i valori delle map non sono indirizzabili.

Scegli i valori quando gli elementi sono piccoli e vengono sostituiti per intero. Scegli i puntatori quando aggiorni spesso i campi o condividi lo stesso record da più punti. Con i puntatori, una chiave mancante restituisce nil, quindi ptrs["nope"].Score va in panic.

Le map di slice funzionano allo stesso modo per gli append: groups[k] = append(groups[k], v) non ha bisogno di inizializzazione, perché una chiave mancante dà una slice nil e append gestisce il nil.

Le map si comportano come riferimenti

Un valore map fa riferimento a dati condivisi. Assegnare una map o passarla a una funzione non copia gli elementi: entrambe le variabili vedono la stessa map.

Per questo una funzione può riempire una map senza restituirla, a differenza di una slice a cui fa append.

Il panic della map nil

Lo zero value di una map è nil. Una map nil si legge come una map vuota, ma scriverci va in panic.

Output:

0 0
recovered: assignment to entry in nil map

Il caso della struct è quello che morde nella pratica. Inizializza i campi map in un costruttore (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) o in modo pigro prima della prima scrittura.

Accesso concorrente

Le map non sono sicure per l'uso concorrente. Se una goroutine scrive mentre un'altra legge o scrive, il runtime può fermare il programma con fatal error: concurrent map writes (o concurrent map read and map write). È un errore fatale, non un panic, quindi recover non può catturarlo.

Proteggi la map con un mutex:

Stampa sempre 50 50. Usa sync.RWMutex quando le letture sono molte più delle scritture. sync.Map esiste per due casi ristretti (chiavi scritte una volta e lette molte volte, oppure goroutine che lavorano su chiavi disgiunte); per tutto il resto un mutex con una map normale è più semplice e di solito più veloce. Approfondisci in mutex.

Riferimento rapido

OperazioneCodice
Crearem := map[string]int{} oppure make(map[string]int)
Inserire o aggiornarem[k] = v
Leggere (zero se manca)v := m[k]
Controllare la presenzav, ok := m[k]
Eliminaredelete(m, k)
Rimuovere tuttoclear(m) (Go 1.21)
Dimensionelen(m)
Chiavi ordinateslices.Sorted(maps.Keys(m)) (Go 1.23)
Copiaremaps.Clone(m)
Confrontaremaps.Equal(a, b)

Una map con valori struct{} è anche il tipo set di Go; vedi set.

Errori comuni

  • Scrivere in una map nil. Creala sempre con make, anche per i campi map nelle struct.
  • Contare sull'ordine di iterazione. Ordina le chiavi.
  • Usare m[k] != 0 come test di presenza. Usa comma-ok.
  • Modificare un campo di una struct tramite m[k].Field. Copia fuori e riscrivi, oppure memorizza puntatori.
  • Condividere una map tra goroutine senza lock. Il crash non si può recuperare.

Domande frequenti

Come controllo se una chiave esiste in una map di Go?

Usa la forma a due valori della ricerca: v, ok := m[key]. ok vale true quando la chiave è presente e false quando non lo è, e in quel caso v è lo zero value. Leggere solo m[key] non permette di distinguere una chiave mancante da una chiave memorizzata con lo zero value.

Perché l'ordine di iterazione delle map in Go è casuale?

Il linguaggio non definisce un ordine, e il runtime fa partire di proposito ogni range da una posizione casuale così i programmi non possono finire per dipendere da un ordine. Per iterare in ordine di chiave, raccogli e ordina le chiavi: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).

Come ottengo tutte le chiavi di una map in Go?

Da Go 1.23, maps.Keys(m) restituisce un iteratore; trasformalo in una slice con slices.Collect(maps.Keys(m)), o in una slice ordinata con slices.Sorted(maps.Keys(m)). Prima della 1.23, cicla con for k := range m e aggiungi ogni chiave a una slice.

Perché scrivere in una map va in panic con "assignment to entry in nil map"?

La variabile map è stata dichiarata ma mai creata: var m map[string]int è nil. Leggere da una map nil restituisce zero value, ma scriverci va in panic. Creala prima con m = make(map[string]int) o con un letterale m := map[string]int{}. Un campo map dentro una struct ha bisogno della stessa inizializzazione.

Le map di Go sono sicure per l'uso concorrente?

No. Le scritture concorrenti, o una scrittura concorrente con delle letture, possono far crashare il programma con fatal error: concurrent map writes, che recover non può catturare. Proteggi la map con un sync.Mutex o un sync.RWMutex, oppure usa sync.Map nei casi specifici per cui è pensata.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA