Как создание большой строки из файла влияет на использование памяти
Что проверяют
Знаете ли вы, во что обходится в .NET строка на сотни мегабайт: сколько она реально весит, где живёт и почему её не видно в обычных сборках мусора. Следом почти всегда спрашивают, как прочитать такой файл, не держа его целиком в памяти.
Как отвечать
Начните с арифметики. string хранит символы в UTF-16, по два байта на char. Файл на 200 МБ в UTF-8 с латиницей превращается в строку примерно на 400 МБ. File.ReadAllText ещё и собирает её через промежуточный буфер, так что пик потребления выше итогового размера.
Дальше про кучу:
- LOH: объекты от 85 000 байт попадают в Large Object Heap, который собирается только вместе с поколением 2 и по умолчанию не уплотняется. Итог: фрагментация и рост памяти процесса;
- копии:
Substring,Split,Replace,ToUpperнад большой строкой порождают новые большие строки. ОдинSplitпо строкам как минимум удваивает потребление; - предел: длина строки ограничена примерно миллиардом символов, файл крупнее просто не влезет, будет
OutOfMemoryExceptionдаже при свободной памяти.
Потом альтернативы. File.ReadLines возвращает ленивый IEnumerable<string> и держит в памяти одну строку. StreamReader с ReadLineAsync даёт асинхронный вариант. Для разбора без аллокаций подходят Span<char>, ReadOnlyMemory<char>, буферы из ArrayPool<char>.Shared и System.IO.Pipelines. JSON читается потоком через Utf8JsonReader или JsonSerializer.DeserializeAsyncEnumerable, без промежуточной строки. Для случайного доступа к огромному файлу есть MemoryMappedFile.
Где спотыкаются
Отвечают про StringBuilder. Он решает проблему склейки в цикле, а не чтения файла, и его ToString() всё равно создаёт одну большую строку в LOH.
Называют это утечкой памяти. Строка освободится, когда на неё пропадут ссылки. Проблема в пике потребления, фрагментации LOH и длинных паузах полной сборки.
Считают, что строка весит столько же, сколько файл. Про UTF-16 и удвоение забывают, а в контейнере с жёстким лимитом именно это приводит к падению.