Как скопировать все значения большой мапы в Go
Что проверяют
Знаете ли вы, что мапа в Go это ссылка на внутреннюю структуру и присваивание ничего не копирует. Слово «большой» в вопросе намекает на память, предвыделение и конкурентный доступ. Следом спрашивают, что будет, если в мапе лежат указатели или слайсы.
Как отвечать
Базовый ответ с Go 1.21 это пакет maps:
maps.Clone(src): новая мапа с теми же парами, дляnilвозвращаетnil;maps.Copy(dst, src): дописывает пары в существующую мапу, совпадающие ключи перезаписываются;- ручной цикл:
dst := make(map[K]V, len(src)), затемfor k, v := range src { dst[k] = v }. Подсказка ёмкости вmakeизбавляет от многократного роста на миллионах ключей.
Если нужны только значения: slices.Collect(maps.Values(m)) с Go 1.23, где maps.Values возвращает итератор. Порядок при этом случайный.
Дальше три оговорки, ради которых вопрос и задают:
- копия поверхностная: если значения это указатели, слайсы или вложенные мапы, обе копии ссылаются на одни данные. Нужна глубокая копия по полям;
- конкурентность: если в мапу параллельно пишут, обход во время копирования уронит процесс фатальной ошибкой вроде
concurrent map iteration and map write, иrecoverеё не перехватит. Копируйте подRLockуsync.RWMutex; - память: на время копирования потребление удваивается. Если снимки нужны часто, выгоднее хранить мапу за
atomic.Pointer, при записи подменять целиком, а читателям отдавать неизменяемый снимок без копирования.
Полезная деталь: мапа не отдаёт память после удаления ключей. Чтобы её вернуть, создают новую мапу по размеру оставшихся данных и переносят пары туда.
Где спотыкаются
Пишут copied := original и считают, что получили копию. Изменения в одной видны в другой.
Забывают про поверхностное копирование и правят структуру в «копии» по указателю, меняя оригинал.
Копируют без блокировки в сервисе под нагрузкой и получают падение процесса, которое в тестах не воспроизводится.