Ответ
В Go map
реализована как хеш-таблица. Её расширение происходит автоматически, когда количество элементов становится слишком большим для текущего размера. Этот процесс называется рехешированием (rehashing).
Триггер для расширения
Расширение запускается, когда коэффициент загрузки (load factor) превышает определённое пороговое значение. В текущей реализации Go это значение равно 6.5.
- Коэффициент загрузки =
количество элементов
/количество бакетов (корзин)
.
Процесс расширения
- Выделение памяти: Создаётся новая, обычно в два раза большая, таблица бакетов.
- Инкрементальное рехеширование: Вместо того чтобы останавливать все операции и перемещать все элементы сразу (что вызвало бы большую задержку), Go использует инкрементальное (постепенное) рехеширование. Старые данные переносятся из старых бакетов в новые постепенно, во время операций записи, чтения или удаления элементов из мапы. Это позволяет избежать долгих пауз "stop-the-world".
Важные особенности и следствия
- Производительность: Рехеширование — это дорогая операция. Если вы заранее знаете примерное количество элементов, которые будут храниться в мапе, лучше сразу создать её с нужной ёмкостью, чтобы избежать многократных расширений:
// Создаем мапу с预-выделенной памятью на 1000 элементов m := make(map[string]int, 1000)
- Нестабильность указателей: Из-за рехеширования элементы могут перемещаться в памяти. Поэтому в Go запрещено брать адрес элемента мапы (
&m[key]
). Компилятор выдаст ошибку, так как такой указатель мог бы стать невалидным после очередной вставки. - Порядок итерации: Порядок итерации по элементам
map
не гарантирован и может меняться после каждого рехеширования. На это нельзя полагаться в логике программы.