map
Чему вы научитесь:
- хранить пары «ключ — значение» в
mapи находить значение по ключу; - отличать отсутствующий ключ от нулевого значения;
- обходить
mapв предсказуемом порядке; - объяснять, почему запись в nil-map вызывает панику, а чтение — нет.

Представьте телефонную книгу. Вы ищете в ней не «седьмую запись сверху», а имя: находите «Анна» — и рядом видите её номер. Имя здесь — ключ, по которому ищут, а номер — значение, которое находят. Листать книгу целиком ради одного номера не нужно.
Теперь посмотрим, как та же задача выглядит без такой книги. Цены товаров можно хранить в двух срезах: в одном названия, в другом цены. Чтобы узнать цену груши, приходится пройти первый срез с начала и запомнить номер найденного элемента. Для таких данных в Go есть map — набор пар, в котором по ключу сразу находится значение:
package main
import "fmt"
func main() {
prices := map[string]int{
"pear": 70,
"plum": 40,
}
fmt.Println(prices["pear"]) // 70
prices["kiwi"] = 90
fmt.Println(len(prices)) // 3
}Давайте разберём пример по строкам:
- Литерал создаёт
mapс двумя парами: груша стоит70, слива —40. prices["pear"]находит цену груши, и программа печатает70.prices["kiwi"] = 90добавляет третью пару, поэтомуlenвозвращает3.
Тип и литерал
Тип map[string]int читается по частям:
map— ключевое слово: «набор пар ключ — значение»;[string]— ключи типаstring;int— значения типаint.
Такая запись map прямо в коде — тоже литерал: в его фигурных скобках перечислены пары «ключ: значение». Значение по ключу читают и записывают в квадратных скобках, как элемент среза: prices["pear"].
Запись по ключу
Запись prices[k] = v подчиняется таким правилам:
- запись по новому ключу добавляет пару;
- запись по существующему ключу заменяет её значение;
- каждый ключ встречается в
mapодин раз; - функция
lenвозвращает число пар.
Здесь map строже телефонной книги: в книге могут оказаться две записи «Анна», а в map второй такой ключ не появится. Ключом может быть строка, число или bool, но не срез: тип map[[]int]string не скомпилируется.
Пустая map
Пустую map можно создать двумя способами. Результат у них одинаковый: map без пар, в которую можно сразу писать.
Функция make
Вызов make(map[string]int) создаёт пустую map, как make создаёт срез. В скобках — только тип map. Вторым аргументом можно подсказать, сколько пар вы ожидаете: make(map[string]int, 100). Это не ограничение: map вырастет и дальше.
make выбирают, когда map начинается пустой и заполняется позже, например в цикле.
Литерал без пар
Литерал map[string]int{} даёт тот же результат, что и make(map[string]int). Фигурные скобки здесь пустые, но в них можно сразу перечислить пары, как в первом примере. Поэтому литерал удобен, когда часть пар известна заранее.
Быстрый поиск
При поиске по ключу Go не перебирает пары по одной, как цикл по срезу. Время чтения почти не зависит от того, сколько пар хранит map: десять или миллион. Поэтому map выбирают, когда значения нужно часто находить по имени, номеру или другому ключу.
Go считает по ключу число — хеш — и по нему решает, куда положить пару. Добавляйте пары кнопкой и следите, что случится, когда девятой паре не хватит места.
В map 8 пар. Пока их не больше 8, map — одна группа на 8 ячеек, и пары лежат подряд. Нажмите кнопку.
Пары лежат в группах по восемь ячеек. Когда свободных ячеек становится мало, map удваивает их число и раскладывает все пары заново по хешу.
Отсутствующий ключ
Если имени в телефонной книге нет, map не сообщает об ошибке. Чтение по отсутствующему ключу возвращает нулевое значение типа значений:
0дляint;""дляstring.
Удаление пары
Встроенная функция delete удаляет пару по ключу, а для отсутствующего ключа ничего не делает:
stock := make(map[string]int)
stock["pear"] = 3
fmt.Println(stock["kiwi"]) // 0
delete(stock, "pear")
delete(stock, "lime")
fmt.Println(len(stock)) // 0Давайте разберём пример по шагам:
- Киви на складе никогда не было, поэтому чтение
stock["kiwi"]возвращает0. - Первый
deleteудаляет грушу. - Второй
deleteищет лайм, не находит и ничего не делает. - В
mapне остаётся ни одной пары, иlenпечатает0.
Счётчик без подготовки
Счётчик не нужно заводить заранее благодаря нулевому значению:
counts := map[string]int{}
counts["go"]++
counts["go"]++
fmt.Println(counts["go"]) // 2Разберём два вызова ++ по порядку:
- Для нового ключа
counts["go"]++читает0, прибавляет единицу и записывает1. - Следующий
++для того же ключа читает уже1и записывает2.
Частая ошибка
В литерале один ключ написан дважды:
prices := map[string]int{
"pear": 70,
"pear": 75, // ✗
}
fmt.Println(prices)duplicate key "pear" in map literalКомпилятор проверяет ключи литерала и не решает за вас, какая цена верная. Программа не соберётся, пока вы не исправите повтор. Оставьте одну пару, а чтобы поменять цену позже, присвойте новое значение: prices["pear"] = 75. Такое присваивание после литерала — не ошибка: оно заменяет значение, как любая запись по существующему ключу.
Главное
map[K]Vхранит пары «ключ — значение»; значение находят по ключу в квадратных скобках.- Запись по новому ключу добавляет пару, по существующему — заменяет значение; каждый ключ встречается один раз.
- Отсутствующий ключ читается как нулевое значение, поэтому
counts[k]++работает и для нового ключа. - Пустую
mapсоздают черезmake(map[string]int)или литералmap[string]int{}.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.