Menu

Mapy w Golang: tworzenie, klucze, iteracja i sortowanie

Mapy w Go przechowują pary klucz-wartość z szybkim wyszukiwaniem. Dowiedz się, jak je tworzyć, sprawdzać przez comma-ok, czy klucz istnieje, usuwać wpisy, iterować (w losowej kolejności), sortować klucze, przechowywać struktury i unikać panik przy mapie nil i współbieżnych zapisach.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Tworzenie i używanie mapy

Typ mapy zapisuje się jako map[KeyType]ValueType. Utwórz mapę literałem albo przez make, a potem odczytuj, zapisuj i usuwaj wpisy po kluczu.

Wynik:

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

Widać tu dwa udogodnienia. Odczyt brakującego klucza zwraca wartość zerową typu wartości (counts['z'] to 0), dzięki czemu liczenie przez m[k]++ działa bez przygotowań. A fmt wypisuje mapy z posortowanymi kluczami, co pomaga przy debugowaniu, ale nic nie mówi o kolejności iteracji.

make(map[K]V, n) przyjmuje opcjonalną wskazówkę rozmiaru. Rezerwuje miejsce na około n wpisów; w odróżnieniu od slice'ów mapa nie ma pojemności, którą można odczytać.

Sprawdzanie, czy klucz istnieje: comma-ok

Ponieważ brakujący klucz odczytuje się jako wartość zerową, m[k] == 0 nie odróżni „brak” od „zapisane jako 0”. Użyj formy dwuwartościowej:

Kształt if v, ok := m[k]; ok { ... } ogranicza zasięg v i ok do tego if. To jedna z najczęstszych linii w kodzie Go.

Usuwanie wpisów

delete(m, key) usuwa wpis. Usunięcie klucza, którego nie ma, nic nie robi, podobnie jak usuwanie z mapy nil. Aby opróżnić całą mapę, Go 1.21 dodało clear(m), które zachowuje zaalokowaną mapę do ponownego użycia.

Usuwanie wpisów w trakcie range po tej samej mapie jest dozwolone i bezpieczne. Wpis usunięty, zanim pętla do niego dotrze, nie zostanie zwrócony.

Iteracja: kolejność jest losowa

for k, v := range m odwiedza każdy wpis raz, w nieokreślonej kolejności. Runtime celowo losuje punkt startowy, więc dwie pętle po tej samej mapie w tym samym programie często dają różne wyniki. Uruchom to kilka razy:

Każdy kod, którego wynik zależy od kolejności w mapie, to błąd czekający na inne uruchomienie. Klasyczny przykład to testy porównujące wypisaną iterację po mapie.

Posortowane klucze

Aby odwiedzić mapę w kolejności kluczy, pobierz klucze, posortuj je i odczytuj mapę po indeksie. Go 1.23 skróciło to do jednej linii dzięki iteratorom z pakietów maps i slices:

W Go 1.22 i starszych maps.Keys nie istniało w bibliotece standardowej. Odpowiednikiem jest pętla:

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

Inne funkcje pomocnicze w maps: maps.Values, maps.Clone (płytka kopia), maps.Equal, maps.Copy(dst, src) i maps.DeleteFunc.

Dozwolone typy kluczy

Klucze muszą być porównywalne przez ==: liczby, stringi, wartości logiczne, wskaźniki, kanały, tablice typów porównywalnych, struktury, których wszystkie pola są porównywalne, i wartości interfejsów. Slice'y, mapy i funkcje nie mogą być kluczami.

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

Klucz w postaci struktury to idiomatyczny sposób na indeksowanie kilkoma wartościami naraz, zamiast sklejania stringów.

Klucze interfejsowe kompilują się nawet wtedy, gdy typ dynamiczny nie jest porównywalny, a potem wywołują panikę w czasie działania: zapisanie []int w map[any]int kończy się błędem runtime error: hash of unhashable type []int.

Klucze zmiennoprzecinkowe działają, ale NaN nie jest równe samemu sobie, więc klucz NaN można wstawiać wielokrotnie i nigdy go nie odczytać. Unikaj kluczy typu float.

Mapa struktur

Mapa może przechowywać struktury, ale nie można przypisać wartości do pola struktury zapisanej w mapie, bo wartości w mapie nie są adresowalne.

Wybierz wartości, gdy wpisy są małe i podmieniane w całości. Wybierz wskaźniki, gdy często aktualizujesz pola albo współdzielisz ten sam rekord z kilku miejsc. Przy wskaźnikach brakujący klucz zwraca nil, więc ptrs["nope"].Score wywołuje panikę.

Mapy slice'ów działają tak samo przy dołączaniu: groups[k] = append(groups[k], v) nie wymaga inicjalizacji, bo brakujący klucz daje slice nil, a append radzi sobie z nil.

Mapy zachowują się jak referencje

Wartość mapy odwołuje się do współdzielonych danych. Przypisanie mapy albo przekazanie jej do funkcji nie kopiuje wpisów: obie zmienne widzą tę samą mapę.

Dlatego funkcja może wypełnić mapę bez jej zwracania, inaczej niż slice, do którego dołącza elementy.

Panika przy mapie nil

Wartość zerowa mapy to nil. Mapa nil przy odczycie zachowuje się jak pusta mapa, ale zapis do niej wywołuje panikę.

Wynik:

0 0
recovered: assignment to entry in nil map

W praktyce najczęściej gryzie przypadek ze strukturą. Inicjalizuj pola typu mapa w konstruktorze (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) albo leniwie przed pierwszym zapisem.

Dostęp współbieżny

Mapy nie są bezpieczne przy współbieżnym użyciu. Jeśli jedna gorutyna zapisuje, a inna czyta lub zapisuje, runtime może zatrzymać program z fatal error: concurrent map writes (albo concurrent map read and map write). To błąd krytyczny, a nie panika, więc recover go nie złapie.

Chroń mapę mutexem:

To zawsze wypisuje 50 50. Użyj sync.RWMutex, gdy odczytów jest znacznie więcej niż zapisów. sync.Map istnieje dla dwóch wąskich przypadków (klucze zapisywane raz i czytane wiele razy albo gorutyny pracujące na rozłącznych kluczach); we wszystkich innych mutex i zwykła mapa są prostsze i zwykle szybsze. Więcej na ten temat w artykule o mutexie.

Ściągawka

OperacjaKod
Utworzeniem := map[string]int{} albo make(map[string]int)
Wstawienie lub aktualizacjam[k] = v
Odczyt (zero, jeśli brak)v := m[k]
Sprawdzenie obecnościv, ok := m[k]
Usunięciedelete(m, k)
Usunięcie wszystkiegoclear(m) (Go 1.21)
Rozmiarlen(m)
Posortowane kluczeslices.Sorted(maps.Keys(m)) (Go 1.23)
Kopiamaps.Clone(m)
Porównaniemaps.Equal(a, b)

Mapa z wartościami struct{} to w Go także typ zbioru; zobacz zbiory.

Częste błędy

  • Zapis do mapy nil. Zawsze twórz ją przez make, także pola typu mapa w strukturach.
  • Poleganie na kolejności iteracji. Posortuj klucze.
  • Używanie m[k] != 0 jako testu obecności. Użyj comma-ok.
  • Modyfikowanie pola struktury przez m[k].Field. Skopiuj wartość, zmień i zapisz z powrotem albo przechowuj wskaźniki.
  • Współdzielenie mapy między gorutynami bez blokady. Tej awarii nie da się przechwycić.

Najczęściej zadawane pytania

Jak sprawdzić, czy klucz istnieje w mapie w Go?

Użyj dwuwartościowej formy odczytu: v, ok := m[key]. ok ma wartość true, gdy klucz jest obecny, i false, gdy go nie ma, a wtedy v ma wartość zerową. Samo odczytanie m[key] nie odróżni brakującego klucza od klucza zapisanego z wartością zerową.

Dlaczego kolejność iteracji po mapie w Go jest losowa?

Język nie definiuje kolejności, a runtime celowo zaczyna każde range w losowym miejscu, żeby programy nie mogły od niej zależeć. Aby iterować w kolejności kluczy, zbierz je i posortuj: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).

Jak pobrać wszystkie klucze mapy w Go?

Od Go 1.23 maps.Keys(m) zwraca iterator; zamień go na slice przez slices.Collect(maps.Keys(m)) albo na posortowany slice przez slices.Sorted(maps.Keys(m)). Przed 1.23 przejdź pętlą for k := range m i dołączaj każdy klucz do slice'a.

Dlaczego zapis do mapy kończy się paniką "assignment to entry in nil map"?

Zmienna mapy została zadeklarowana, ale mapa nigdy nie została utworzona: var m map[string]int to nil. Odczyt z mapy nil zwraca wartości zerowe, ale zapis wywołuje panikę. Najpierw utwórz mapę przez m = make(map[string]int) albo literał m := map[string]int{}. Pole typu mapa w strukturze wymaga takiej samej inicjalizacji.

Czy mapy w Go są bezpieczne przy współbieżnym użyciu?

Nie. Współbieżne zapisy albo zapis równoległy z odczytami mogą wywrócić program z fatal error: concurrent map writes, czego recover nie złapie. Chroń mapę przez sync.Mutex albo sync.RWMutex lub użyj sync.Map w konkretnych przypadkach, do których została zaprojektowana.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ