---
title: Хэш-таблицы
seo:
  title: Хэш-таблицы — Go Developer
  description: Тема «Хэш-таблицы» для собеседования Go Developer. Что такое map? Как устроен в Go? Что такое хеш-функция?
---

[Все темы Go Developer](/go-developer)

## <strong>Что такое map? Как устроен в Go?</strong> [#q-14bee738d69b8148bec5d86fa531443a]

<strong>Map</strong> — это встроенный тип данных в Go, который представляет
собой неупорядоченную коллекцию пар ключ-значение. Map обеспечивает эффективный
доступ к значениям по их ключам.

#### Основные характеристики&#58;

1. <strong>Объявление и инициализация</strong>&#58;

   ```go
   var m map[string]int             // Объявление без инициализации (nil map)
   m = make(map[string]int)         // Инициализация с помощью make
   n := map[string]int{"a": 1, "b": 2} // Объявление и инициализация с литералом
   ```

1. <strong>Добавление и получение элементов</strong>&#58;

   ```go
   m["key1"] = 100                // Добавление значения по ключу
   value := m["key1"]             // Получение значения по ключу
   ```

1. <strong>Удаление элементов</strong>&#58;

   ```go
   delete(m, "key1")              // Удаление элемента по ключу
   ```

1. <strong>Проверка существования ключа</strong>&#58;

   ```go
   value, exists := m["key1"]
   if exists {
       fmt.Println("Key exists with value:", value)
   } else {
       fmt.Println("Key does not exist")
   }
   ```

#### Пример использования&#58;

```go
package main

import "fmt"

func main() {
    m := make(map[string]int) // Создание map
    m["a"] = 1                // Добавление элемента
    m["b"] = 2

    fmt.Println(m["a"]) // Получение значения по ключу

    value, exists := m["c"] // Проверка существования ключа
    if exists {
        fmt.Println("c exists with value:", value)
    } else {
        fmt.Println("c does not exist")
    }

    delete(m, "a") // Удаление элемента
    fmt.Println(m)
}
```

Map в Go обеспечивает удобный и эффективный способ хранения и управления парами ключ-значение.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Что такое хеш-функция?</strong> [#q-14bee738d69b81a1bf61d6d68904ab50]

<strong>Хеш-функция</strong> — это функция, которая принимает входные данные
(например, строку или файл) и возвращает фиксированную длину значения, которое
называется хешем. Это значение представляет собой "отпечаток" или "подпись"
входных данных.

#### Основные характеристики хеш-функций&#58;

1. <strong>Определенность</strong>&#58; Для одного и того же входного значения
   хеш-функция всегда возвращает один и тот же хеш.

1. <strong>Быстрота вычисления</strong>&#58; Хеш-функция должна быстро вычислять
   хеш для любых входных данных.

1. <strong>Фиксированная длина</strong>&#58; Независимо от размера входных
   данных, хеш имеет фиксированную длину.

1. <strong>Устойчивость к коллизиям</strong>&#58; Хеш-функция должна
   минимизировать вероятность того, что два разных входных значения будут иметь
   одинаковый хеш (это называется коллизией).

1. <strong>Сложность обратного преобразования</strong>&#58; Для безопасных
   хеш-функций должно быть сложно восстановить исходные данные по хешу.

#### Примеры использования хеш-функций&#58;

1. <strong>Хеш-таблицы</strong>&#58; Для быстрого поиска, вставки и удаления
   элементов.

1. <strong>Криптография</strong>&#58; Для проверки целостности данных и цифровых
   подписей.

1. <strong>Контроль целостности данных</strong>&#58; Для проверки изменений в
   файлах (например, контрольные суммы).

#### Пример простой хеш-функции (не криптографической)&#58;

```go
func simpleHash(s string) int {
    hash := 0
    for _, char := range s {
        hash = 31*hash + int(char)
    }
    return hash
}
```

#### Криптографическая хеш-функция&#58;

Пример использования криптографической хеш-функции <code>SHA&#45;256</code> в Go&#58;

```go
package main

import (
    "crypto/sha256"
    "fmt"
)

func main() {
    data := "Hello, World!"
    hash := sha256.Sum256([]byte(data))
    fmt.Printf("SHA-256 hash: %x\\n", hash)
}
```

Хеш-функции играют ключевую роль в различных областях информатики, обеспечивая эффективные и безопасные способы обработки данных.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Почему нельзя брать ссылку на значение, хранящееся по ключу в map?</strong> [#q-14bee738d69b81039da4c60640546c3e]

В Go нельзя брать ссылку на значение, хранящееся по ключу в map, потому что значения в map могут перемещаться в памяти при изменении map (например, при добавлении новых элементов). Это сделано для обеспечения эффективного управления памятью и производительности.

#### Пример&#58;

```go
m := map[string]int{"a": 1}
value := m["a"]
valuePtr := &value // Ссылка на копию значения, а не на оригинал
```

Из-за возможных перемещений значений в памяти ссылка может стать недействительной или привести к некорректным данным, поэтому Go не позволяет напрямую брать ссылки на элементы map.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Что такое эвакуация, и в каком случае она будет происходить?</strong> [#q-14bee738d69b81c79f2de71fdad4efc6]

Эвакуация — термин прежней реализации map&#58; элементы постепенно переносились между бакетами при росте или реорганизации таблицы. В Go 1.24 реализацию заменили на Swiss Tables; детали роста зависят от версии runtime. Ни бакеты, ни пороги роста не являются гарантией языка.

#### Когда происходит эвакуация&#58;

- <strong>При добавлении новых элементов</strong>, когда текущая емкость map
  недостаточна для их размещения.

- <strong>При увеличении плотности заполнения</strong> (load factor), чтобы
  сохранить производительность операций поиска и вставки.

Эвакуация помогает поддерживать эффективность работы map, обеспечивая равномерное распределение элементов и минимизируя коллизии.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Какие есть особенности синтаксиса получения и записи значений в map?</strong> [#q-14bee738d69b81d8a33af0db4b14b63a]

Синтаксис получения и записи значений в <code>map</code> в Go&#58;

#### Запись значений&#58;

```go
m := make(map[string]int)
m["key"] = 42  // Запись значения 42 по ключу "key"
```

#### Получение значений&#58;

```go
value := m["key"]  // Получение значения по ключу "key"
```

#### Проверка существования ключа&#58;

```go
value, exists := m["key"]  // Получение значения и проверка существования ключа
if exists {
    fmt.Println("Key exists with value:", value)
} else {
    fmt.Println("Key does not exist")
}
```

#### Удаление значения&#58;

```go
delete(m, "key")  // Удаление значения по ключу "key"
```

#### Особенности&#58;

- <strong>Чтение значения</strong>&#58; Если ключ не существует, возвращается
  zero-value типа значения.

- <strong>Запись значения</strong>&#58; Автоматически добавляет ключ, если он не
  существует.

- <strong>Удаление значения</strong>&#58; Безопасно, даже если ключ не
  существует.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Как происходит поиск по ключу в map?</strong> [#q-14bee738d69b81ce8766fa6ecb0fcc99]

Поиск по ключу в <code>map</code> в Go осуществляется с помощью хеш-таблицы&#58;

1. <strong>Хеширование ключа</strong>&#58; Ключ проходит через хеш-функцию,
   которая вычисляет хеш-значение.

1. <strong>Определение индекса</strong>&#58; Хеш-значение используется для
   определения индекса в массиве (бакте) внутри map.

1. Поиск кандидатов&#58; runtime использует хеш для выбора группы и затем сравнивает подходящие ключи. Конкретный алгоритм зависит от реализации&#58; описание линейного поиска в бакете относится к прежней map.

#### Пример поиска&#58;

```go
m := map[string]int{"a": 1, "b": 2}
value := m["a"] // Поиск значения по ключу "a"
```

#### Особенности&#58;

- <strong>Эффективность</strong>&#58; Поиск обычно выполняется за константное
  время O(1), но может ухудшиться до O(n) в случае коллизий.

- <strong>Безопасность</strong>&#58; Возвращает zero-value типа значения, если
  ключ не найден.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Каков порядок перебора map?</strong> [#q-14bee738d69b815c99f0eaab900d993e]

Порядок перебора map не определен спецификацией и может меняться между итерациями. На случайность распределения или обязательную смену порядка полагаться нельзя.

#### Пример перебора map&#58;

```go
m := map[string]int{"a": 1, "b": 2, "c": 3}

for key, value := range m {
    fmt.Println(key, value)
}
```

#### Особенности&#58;

- <strong>Случайный порядок</strong>&#58; Порядок перебора не гарантируется и
  может различаться при каждом запуске программы.

- <strong>Никакой упорядоченности</strong>&#58; Нельзя полагаться на какой-либо
  определенный порядок при переборе элементов.

Этот подход помогает избежать зависимости кода от конкретного порядка элементов в map и способствует равномерному распределению элементов при вставках и удалениях.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Что будет происходить при конкурентной записи в map? Как можно решить эту проблему?</strong> [#q-14bee738d69b8174a228f3b3104bb129]

В Go стандартные map не являются безопасными для использования в многопоточной среде. При конкурентной записи могут происходить следующие проблемы&#58;

1. <strong>Гонка данных</strong>&#58; Одновременные записи могут привести к
   некорректным состояниям map.

1. Ошибка runtime&#58; несинхронизированные конкурентные записи или чтение одновременно с записью создают гонку данных; runtime может аварийно завершить программу. Это не обычная panic, которую можно обработать recover. Параллельные чтения без записей допустимы.

#### Решение проблемы&#58;

Для безопасной работы с map в многопоточной среде используйте <code>sync&#46;Map</code> или механизмы синхронизации, такие как мьютексы (<code>sync&#46;Mutex</code>).

#### Использование <code>sync&#46;Map</code>&#58;

```go
import (
    "sync"
)

var m sync.Map

// Запись значения
m.Store("key", 42)

// Чтение значения
value, ok := m.Load("key")
if ok {
    fmt.Println("Value:", value)
}
```

#### Использование мьютекса&#58;

```go
import (
    "sync"
)

var (
    m = make(map[string]int)
    mu sync.Mutex
)

// Запись значения с мьютексом
mu.Lock()
m["key"] = 42
mu.Unlock()

// Чтение значения с мьютексом
mu.Lock()
value := m["key"]
mu.Unlock()
fmt.Println("Value:", value)
```

#### Особенности&#58;

- <code>sync&#46;Map</code>&#58; Предназначен специально для конкурентного
  использования, имеет методы для безопасного доступа.

- <code>Мьютексы</code>&#58; Обеспечивают явную синхронизацию доступа к map,
  обеспечивая безопасность при чтении и записи.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::

---

## <strong>Как защититься от ошибки во время конкурентной записи в map?</strong> [#q-14bee738d69b81138fcbcb6de51f3e8c]

Способы защиты от ошибок во время конкурентной записи в <code>map</code>&#58;

{/* prettier-ignore */}
1. <strong>Использование</strong> <code>sync&#46;Map</code>&#58;

    - <code>sync&#46;Map</code> в стандартной библиотеке Go специально разработан для безопасного использования в многопоточной среде.

    ```go
    import (
        "sync"
    )

    var m sync.Map

    // Запись значения
    m.Store("key", 42)

    // Чтение значения
    value, ok := m.Load("key")
    if ok {
        fmt.Println("Value:", value)
    }
    ```

1. <strong>Использование мьютексов (</strong><code>sync&#46;Mutex</code><strong>)</strong>&#58;

    - Использование мьютексов для явной синхронизации доступа к map.

    ```go
    import (
        "sync"
    )

    var (
        m  = make(map[string]int)
        mu sync.Mutex
    )

    // Запись значения с мьютексом
    func safeWrite(key string, value int) {
        mu.Lock()
        defer mu.Unlock()
        m[key] = value
    }

    // Чтение значения с мьютексом
    func safeRead(key string) int {
        mu.Lock()
        defer mu.Unlock()
        return m[key]
    }

    func main() {
        safeWrite("key", 42)
        fmt.Println("Value:", safeRead("key"))
    }
    ```

#### Особенности&#58;

- <code>sync&#46;Map</code>&#58; Простой в использовании для конкурентного
  доступа, но может быть менее эффективен для некоторых сценариев по сравнению с
  мьютексами.

- <strong>Мьютексы</strong>&#58; Предоставляют явную и гибкую синхронизацию, но
  требуют аккуратного управления блокировками и могут усложнять код.

:::note[Ссылки для изучения]

1. [Мапы в Go&#58; Уровень Pro](https://habr.com/ru/companies/avito/articles/774618/)
   :::
