Как работает 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"}`), &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>