Как работает Map


В языке программирования Go тип map представляет собой хеш-таблицу, позволяющую хранить пары «ключ–значение». Это структура данных, которая обеспечивает быстрый доступ к значениям по ключу, добавление, удаление и проверку наличия элемента — с амортизированной сложностью O(1). map — встроенный тип, аналог словаря (dictionary) в Python или объекта в JavaScript.

1. Объявление и инициализация

Объявление пустой карты с использованием make:

python</p><p>m := make(map[string]int)</p><p>

Создаёт пустую карту, где ключи — строки, а значения — целые числа.

Инициализация литералом:

python</p><p>m := map[string]int{</p><p>"Alice": 25,</p><p>"Bob": 30,</p><p>}</p><p>

2. Ключи и значения

Ключом может быть любой тип, удовлетворяющий требованиям:

  • сравниваемый (то есть поддерживает операции == и !=);
  • постоянный хеш на протяжении времени (не изменяется).

Допустимые типы ключей:

  • целые (int, uint64)
  • строки
  • булевы (bool)
  • указатели
  • структуры (если все поля сравниваемы)
  • интерфейсы (если их значения сравниваемы)

Недопустимые типы ключей:

  • слайсы ([]int)
  • мапы (map[string]int)
  • функции

Значением может быть любой тип, включая:

  • числа
  • строки
  • структуры
  • указатели
  • интерфейсы
  • даже другие мапы или слайсы

3. Доступ к элементам

Чтение значения по ключу:

python</p><p>value := m["Alice"]</p><p>

Если ключ отсутствует, возвращается нулевое значение типа значения (0 для int, "" для string и т.д.).

Чтобы узнать, существует ли ключ, используется идиома с двумя возвращаемыми значениями:

python</p><p>val, ok := m["Alice"]</p><p>if ok {</p><p>fmt.Println("Найдено:", val)</p><p>} else {</p><p>fmt.Println("Ключ отсутствует")</p><p>}</p><p>

4. Добавление и изменение

python</p><p>m["Charlie"] = 40 // добавление нового ключа</p><p>m["Alice"] = 26 // обновление существующего</p><p>

5. Удаление элемента

Для удаления используется встроенная функция delete:

python</p><p>delete(m, "Bob")</p><p>

Если ключ отсутствует — ничего не происходит.

6. Итерирование по map

Итерация производится через range:

python</p><p>for key, value := range m {</p><p>fmt.Println(key, value)</p><p>}</p><p>

Порядок обхода неопределённый и не гарантирован. Каждый запуск может дать другой порядок.

7. Длина карты

Функция len возвращает количество пар в мапе:

python</p><p>count := len(m)</p><p>

8. map и nil

Карта может быть nil, если она не инициализирована:

python</p><p>var m map[string]int</p><p>fmt.Println(m == nil) // true</p><p>

  • Чтение из nil-карты безопасно (вернёт ноль и false).
  • Запись вызовет панику:

python<br></p><p>m["x"] = 10 // panic: assignment to entry in nil map</p><p>

Поэтому нужно инициализировать через make() перед использованием.

9. Копирование map

Копирование переменной карты копирует только ссылку, а не содержимое. То есть, обе переменные указывают на один и тот же объект:

python</p><p>m1 := map[string]int{"a": 1}</p><p>m2 := m1</p><p>m2["a"] = 100</p><p>fmt.Println(m1["a"]) // 100</p><p>

Изменения через m2 будут видны в m1.

10. Функции с map как аргумент

Поскольку map передаётся по ссылке (как слайс), изменения внутри функции видны снаружи:

python</p><p>func update(m map[string]int) {</p><p>m["new"] = 1</p><p>}</p><p></p><p>m := map[string]int{}</p><p>update(m)</p><p>fmt.Println(m) // map[new:1]</p><p>

11. map как значение интерфейса

Карты можно хранить как значения в интерфейсах:

python</p><p>var i interface{} = map[string]string{"hello": "world"}</p><p>

При этом важно помнить, что интерфейс копирует карту по значению, то есть интерфейсная переменная содержит указатель на ту же карту.

12. Сравнение карт

Мапы нельзя сравнивать напрямую (кроме == nil), потому что порядок хранения и хеш-функции могут отличаться:

python</p><p>m1 := map[string]int{"a": 1}</p><p>// m2 := map[string]int{"a": 1}</p><p>// fmt.Println(m1 == m2) // ошибка компиляции</p><p>

Для сравнения необходимо вручную сравнивать ключи и значения.

13. Встроенная хеш-таблица Go

Внутри мапа реализована как хеш-таблица с открытой адресацией и несколькими уровнями оптимизаций, включая:

  • использование 8-слотовых бакетов;
  • ускорение чтения с использованием оптимизированных хеш-функций;
  • псевдослучайный порядок итерации для предотвращения зависимостей в коде.

Алгоритм устроен так, что в каждом "бакете" хранится до 8 элементов, и при коллизиях Go использует вторичное хеширование или миграцию в другие бакеты. При увеличении количества элементов происходит перехеширование (resize), аналогично другим языкам.

14. Работа с map в многопоточном коде

Обычные карты в Go не являются потокобезопасными. Попытка параллельной записи или чтения может вызвать runtime error: concurrent map writes.

Для безопасной работы:

  • использовать sync.Mutex:

python</p><p>var mu sync.Mutex</p><p>m := make(map[string]int)</p><p></p><p>mu.Lock()</p><p>m["x"] = 1</p><p>mu.Unlock()</p><p>

  • использовать sync.Map, который обеспечивает потокобезопасную работу:

python</p><p>var sm sync.Map</p><p>sm.Store("a", 100)</p><p>val, ok := sm.Load("a")</p><p>

15. Zero value карты

Ноль-значением типа map является nil, и его поведение:

  • чтение из nil — безопасно (вернёт ноль);
  • запись — вызывает панику;
  • len(nilMap) возвращает 0.

16. Сложные ключи

Можно использовать структуры как ключи, если все поля сравнимы:

python</p><p>type Point struct {</p><p>X, Y int</p><p>}</p><p></p><p>m := make(map[Point]string)</p><p>m[Point{1, 2}] = "A"</p><p>

17. Удаление несуществующего ключа

Удаление несуществующего ключа — безопасная операция:

python</p><p>delete(m, "nonexistent") // OK, ничего не делает</p><p>

18. Map внутри структуры

python</p><p>type Config struct {</p><p>Settings map[string]string</p><p>}</p><p>

19. Map как JSON

Карты часто используются при разборе/формировании JSON:

python</p><p>var result map[string]interface{}</p><p>json.Unmarshal([]byte(`{"name":"Bob"}`), &amp;result)</p><p>

20. Пример: частота слов

python</p><p>text := "go go lang go is awesome"</p><p>words := strings.Fields(text)</p><p>freq := make(map[string]int)</p><p></p><p>for _, word := range words {</p><p>freq[word]++</p><p>}</p><p></p><p>for word, count := range freq {</p><p>fmt.Printf("%s: %d\n", word, count)</p><p>}</p><p>