.NET System.OutOfMemoryException для String.Split () файла CSV 120 МБ - PullRequest
6 голосов
/ 01 мая 2009

Я использую C # для чтения CSV-файла размером ~ 120 МБ. Первоначально я выполнял синтаксический анализ, читая его построчно, но недавно решил, что сначала чтение всего содержимого файла в память было в несколько раз быстрее. Анализ уже довольно медленный, потому что CSV имеет запятые, встроенные в кавычки, что означает, что я должен использовать разделение регулярных выражений. Это единственный, который я нашел, который работает надежно:

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,)
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))");
// from http://regexlib.com/REDetails.aspx?regexp_id=621

Чтобы выполнить синтаксический анализ после чтения всего содержимого в память, я делю строку на символ новой строки, чтобы получить массив, содержащий каждую строку. Однако, когда я делаю это для файла размером 120 МБ, я получаю System.OutOfMemoryException. Почему памяти так быстро не хватает, когда на моем компьютере 4 ГБ ОЗУ? Есть ли лучший способ быстро разобрать сложный CSV?

Ответы [ 9 ]

8 голосов
/ 01 мая 2009

Не катите свой собственный парсер, если вам не нужно. Мне повезло с этим:

Быстрый считыватель CSV

Если ничего другого, ты можешь заглянуть под капот и посмотреть, как это делает кто-то еще.

7 голосов
/ 01 мая 2009

Вы можете получить исключение OutOfMemoryException для практически любого размера выделения. Когда вы выделяете часть памяти, вы действительно запрашиваете непрерывную часть памяти требуемого размера. Если это не может быть выполнено, вы увидите исключение OutOfMemoryException.

Вы также должны знать, что, если вы не используете 64-битную Windows, ваша 4 ГБ ОЗУ разделена на 2 ГБ пространства ядра и 2 ГБ пространства пользователя, поэтому ваше приложение .NET не может получить доступ более 2 ГБ по умолчанию.

При выполнении строковых операций в .NET вы рискуете создать много временных строк из-за того, что строки .NET являются неизменяемыми. Поэтому вы можете заметить, что использование памяти резко возрастает.

5 голосов
/ 01 мая 2009

Если у вас есть весь файл, считанный в строку, вам, вероятно, следует использовать StringReader .

StringReader reader = new StringReader(fileContents);
string line;
while ((line = reader.ReadLine()) != null) {
    // Process line
}

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

Редактировать после тестирования

Попытка выше с файлом 140 МБ, где обработка состояла из увеличения переменной длины с помощью line.Length. Это заняло около 1,6 секунд на моем компьютере. После этого я попробовал следующее:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt");
long length = 0;
string line;
while ((line = reader.ReadLine()) != null)
    length += line.Length;

Результат составил около 1 секунды.

Конечно, ваш пробег может отличаться, особенно если вы читаете с сетевого диска или ваша обработка занимает достаточно много времени для поиска жесткого диска в другом месте. Но также, если вы используете FileStream для чтения файла и не буферизуете. StreamReader обеспечивает буферизацию, которая значительно улучшает чтение.

4 голосов
/ 01 мая 2009

Возможно, вам не удастся выделить один объект с таким большим количеством непрерывной памяти, и вы не должны этого ожидать. Потоковая передача - это обычный способ сделать это, но вы правы, что это может быть медленнее (хотя я не думаю, что обычно оно должно быть намного медленнее).

В качестве компромисса вы можете попробовать прочитать большую часть файла (но все же не полностью) сразу с помощью функции, подобной StreamReader.ReadBlock(), и обрабатывать каждую часть по очереди.

1 голос
/ 01 мая 2009

Как говорят другие авторы, OutOfMemory заключается в том, что не может найти непрерывный кусок памяти запрошенного размера.

Тем не менее, вы говорите, что разбирать строку за строкой было в несколько раз быстрее, чем читать все сразу, а затем выполнять обработку. Это имеет смысл, только если вы придерживались наивного подхода блокирующего чтения, например (в псевдокоде):

while(! file.eof() )
{
    string line = file.ReadLine();
    ProcessLine(line);
}

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

0 голосов
/ 23 февраля 2010

Вы должны прочитать кусок в буфер и работать над этим. Затем прочитайте другой фрагмент и т. Д.

Есть много библиотек, которые сделают это эффективно для вас. Я поддерживаю один под названием CsvHelper . Есть много крайних случаев, которые вам нужно обработать, например, когда запятая или конец строки находится в середине поля.

0 голосов
/ 15 мая 2009

Я согласен с большинством здесь, вам нужно использовать потоковую передачу.

Я не знаю, говорил ли кто-нибудь до сих пор, но вы должны взглянуть на метод расширения.

И я точно знаю, что лучше всего разбивать CSV на .NET / CLR это

Этот метод сгенерировал мне + 10GB выходных XML-данных из входного CSV, включая обширные входные фильтры и все, быстрее, чем все, что я видел.

0 голосов
/ 01 мая 2009

Недостаточно памяти в стеке, а не в куче.

Вы можете попробовать перефакторизовать ваше приложение таким образом, чтобы обрабатывать ввод более управляемыми «кусками» данных, а не обрабатывать 120 МБ за раз.

0 голосов
/ 01 мая 2009

Возможно, вам следует попробовать CLR profiler , чтобы определить фактическое использование памяти. Возможно, существуют ограничения памяти, отличные от памяти вашей системы. Например, если это приложение IIS, ваша память ограничена пулами приложений.

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

...