Maps - хеш-таблицы в Go
Maps (карты) в Go - это встроенный ассоциативный тип данных, который хранит пары ключ-значение. Думайте о них как об ассоциативных массивах PHP или объектах JavaScript.
Создание maps
Литерал map
// Пустая map
var m1 map[string]int // nil map, нельзя записывать!
// С инициализацией
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Carol": 92,
}
// Пустая, но инициализированная
empty := map[string]int{}
Make
// Создание с make
ages := make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
// С начальной емкостью (подсказка для оптимизации)
large := make(map[string]int, 1000)
Операции с maps
Запись и чтение
// Запись
m := make(map[string]string)
m["key"] = "value"
// Чтение
value := m["key"]
// Чтение с проверкой существования
value, exists := m["key"]
if exists {
fmt.Println("Найдено:", value)
} else {
fmt.Println("Ключ не найден")
}
Удаление
// Удаление по ключу
delete(m, "key")
// delete безопасен для несуществующих ключей
delete(m, "nonexistent") // не паникует
Итерация
scores := map[string]int{
"Alice": 95,
"Bob": 87,
}
// Порядок итерации случайный!
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
// Только ключи
for name := range scores {
fmt.Println(name)
}
// Только значения
for _, score := range scores {
fmt.Println(score)
}
Ключи и значения
Что может быть ключом?
Ключ должен быть сравнимым типом:
- Все базовые типы (int, string, bool и т.д.)
- Массивы (не слайсы!)
- Структуры (если все поля сравнимы)
- Указатели
- Интерфейсы
// Структура как ключ
type Point struct {
X, Y int
}
grid := make(map[Point]string)
grid[Point{1, 2}] = "A"
grid[Point{3, 4}] = "B"
Практические примеры
Подсчет частоты
func wordFrequency(text string) map[string]int {
words := strings.Fields(text)
freq := make(map[string]int)
for _, word := range words {
freq[word]++ // автоматически 0 + 1 для нового ключа
}
return freq
}
// Использование
text := "go is awesome go is fast"
freq := wordFrequency(text)
// map[awesome:1 fast:1 go:2 is:2]
Группировка
type Person struct {
Name string
Age int
City string
}
func groupByCity(people []Person) map[string][]Person {
groups := make(map[string][]Person)
for _, person := range people {
groups[person.City] = append(groups[person.City], person)
}
return groups
}
Set (множество)
// В Go нет встроенного Set, используем map[T]bool
type Set map[string]bool
func NewSet(items ...string) Set {
s := make(Set)
for _, item := range items {
s[item] = true
}
return s
}
func (s Set) Add(item string) {
s[item] = true
}
func (s Set) Contains(item string) bool {
return s[item]
}
func (s Set) Remove(item string) {
delete(s, item)
}
// Или используем пустую структуру для экономии памяти
type IntSet map[int]struct{}
func (s IntSet) Add(n int) {
s[n] = struct{}{}
}
Maps и конкурентность
С мьютексом
type SafeCounter struct {
mu sync.Mutex
counter map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.counter[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.counter[key]
}
sync.Map
var m sync.Map
// Запись
m.Store("key", "value")
// Чтение
value, ok := m.Load("key")
// Удаление
m.Delete("key")
// Итерация
m.Range(func(key, value interface{}) bool {
fmt.Printf("%v: %v\n", key, value)
return true // продолжить итерацию
})
Паттерны и best practices
Lazy initialization
type Cache struct {
data map[string]string
}
func (c *Cache) Get(key string) string {
if c.data == nil {
c.data = make(map[string]string)
}
return c.data[key]
}
Map of maps
// Двумерная таблица
table := make(map[string]map[string]int)
// Инициализация при необходимости
func setValue(table map[string]map[string]int, row, col string, value int) {
if table[row] == nil {
table[row] = make(map[string]int)
}
table[row][col] = value
}
Кеширование
type Fibonacci struct {
cache map[int]int
}
func (f *Fibonacci) Get(n int) int {
if f.cache == nil {
f.cache = make(map[int]int)
}
if val, exists := f.cache[n]; exists {
return val
}
var result int
if n <= 1 {
result = n
} else {
result = f.Get(n-1) + f.Get(n-2)
}
f.cache[n] = result
return result
}
Производительность
<ComparisonTable data={{ headers: ["Операция", "Сложность", "Комментарий"], rows: [ ["Вставка", "O(1)", "Амортизированное"], ["Чтение", "O(1)", "В среднем"], ["Удаление", "O(1)", "В среднем"], ["Итерация", "O(n)", "n - количество элементов"], ["len()", "O(1)", "Просто чтение поля"] ] }} />
Советы по производительности
// Pre-size для больших maps
bigMap := make(map[string]int, 10000)
// Переиспользование maps
func process(m map[string]int) {
// Очистка map для переиспользования
for k := range m {
delete(m, k)
}
// Теперь map пустая, но память выделена
}
// Struct{} вместо bool для sets
type Set map[string]struct{} // экономит память
Типичные ошибки
Конкурентный доступ
// ПАНИКА!
m := make(map[string]int)
go func() {
for i := 0; i < 1000; i++ {
m["key"] = i
}
}()
go func() {
for i := 0; i < 1000; i++ {
_ = m["key"]
}
}()
Изменение map во время итерации
// Безопасно добавлять
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
m[k+"_new"] = v * 2 // OK
}
// Но порядок непредсказуем
Nil map
var m map[string]int
m["key"] = 1 // ПАНИКА!
// Правильно
m = make(map[string]int)
m["key"] = 1 // OK
JSON и maps
// Сериализация
data := map[string]interface{}{
"name": "Alice",
"age": 30,
"tags": []string{"go", "backend"},
}
jsonBytes, _ := json.Marshal(data)
// Десериализация
var result map[string]interface{}
json.Unmarshal(jsonBytes, &result)
Итоги
- Maps - основная структура для ассоциативных данных
- Ключи должны быть сравнимыми
- Помните про nil maps
- Maps не потокобезопасны
- Порядок итерации случайный
В следующем уроке изучим структуры - пользовательские типы данных!
Типичная ошибка
Считать, что отсутствующий ключ - это ошибка. На самом деле вернётся «нулевое значение» - поэтому и нужен ok.