Ответ
Внутреннее устройство map
в Go — это хэш-таблица, состоящая из массива бакетов (buckets).
Процесс определения бакета и поиска значения выглядит так:
-
Вычисление хэша: Для ключа вычисляется хэш с помощью типо-специфичной хэш-функции. Go выбирает быструю хэш-функцию во время компиляции.
-
Определение бакета: Количество бакетов в
map
всегда является степенью двойки. Это позволяет найти индекс бакета с помощью быстрой побитовой операцииAND
, а не медленной операции остатка от деления (%
).// Псевдокод hashValue := hash(key) // Вместо hashValue % numBuckets используется побитовая операция bucketIndex := hashValue & (numBuckets - 1)
-
Поиск внутри бакета (Tophash): Чтобы избежать дорогостоящего сравнения полных ключей при коллизиях, Go использует оптимизацию tophash. Каждый бакет хранит 8 старших бит хэша (
tophash
) для каждого из своих 8 слотов.- Сначала быстро сравниваются
tophash
искомого ключа со значениямиtophash
в бакете. - Только если
tophash
совпал, происходит полное сравнение ключа, чтобы убедиться, что это не коллизия с другим ключом, имеющим такой жеtophash
.
- Сначала быстро сравниваются
-
Обработка коллизий: Если все 8 слотов в бакете заняты, используется overflow bucket (дополнительный бакет), связанный с основным. Поиск продолжается в нем.
-
Рехеширование (Rehashing): Когда
map
становится слишком заполненной (load factor > 6.5), ее размер удваивается. В Go рехеширование происходит инкрементально: старые бакеты переносятся в новую, увеличенную таблицу постепенно, во время операций записи, а не за один раз, что позволяет избежать долгих пауз (stop-the-world).