исключение при чтении очень большого файла> 300 МБ - PullRequest
0 голосов
/ 12 июня 2009

Моя задача - открыть большой файл в режиме READ & WRITE , и мне нужно найти некоторую часть текста в этом файле путем поиска начальной и конечной точки. Затем мне нужно записать эту искомую область текста в новый файл и удалить эту часть из исходного файла.

Вышеупомянутый процесс я буду делать больше раз. Поэтому я подумал, что для этого процесса будет легко загрузить файл в память с помощью CharBuffer и легко осуществлять поиск по классу MATCHER . Но я получаю HeapSpace исключение во время чтения, хотя я увеличил до 900 МБ, выполнив, как показано ниже java -Xms128m -Xmx900m readLargeFile Мой код

FileChannel fc = new FileInputStream(fFile).getChannel();
CharBuffer chrBuff = Charset.forName("8859_1").newDecoder().decode(fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()));

Для этого кода выше каждый предложил мне, что это плохая идея загрузить все в память и если размер файла 300 МБ значит, это будет 600 МБ из-за charSet.

Итак, выше моя задача, а затем предложите мне несколько эффективных способов. Обратите внимание, что мой размер файла будет больше , и используя JAVA , только я должен делать эти вещи.

Заранее спасибо ...

Ответы [ 5 ]

5 голосов
/ 12 июня 2009

Вы определенно НЕ хотите загружать файл размером 300 МБ в один большой буфер с Java. То, что вы делаете, должно быть более эффективным для больших файлов, чем просто использование обычного ввода-вывода, но когда вы запускаете Matcher для всего файла, отображенного в память, как вы, вы можете очень легко исчерпать память.

Во-первых, ваша кодовая память отображает файл в память ... это будет потреблять 300 Мегабайт памяти в вашем виртуальном адресном пространстве, так как файл mmap заносится в него, хотя это за пределами кучи. (Обратите внимание, что 300 мегабайт виртуального адресного пространства связаны до тех пор, пока MappedByteBuffer не станет сборщиком мусора . См. Обсуждение ниже. JavaDoc для map предупреждает вас об этом. ) Затем вы создаете ByteBuffer, поддерживаемый этим mmap ed файлом. Это должно быть хорошо, так как это просто «просмотр» ed-файла mmap и, следовательно, он должен занимать минимум дополнительной памяти. Это будет маленький объект в куче с «указателем» на большой объект вне кучи. Затем вы декодируете это в CharBuffer, что означает, что вы делаете копию буфера 300 МБ, но вы делаете копию 600 МБ (в куче), потому что char составляет 2 байта.

Чтобы ответить на комментарий и, конечно же, взглянуть на исходный код JDK, при вызове map() в качестве OP вы фактически отображаете весь файл в память. Глядя на собственный код Windows openJDK 6 b14 sun.nio.ch.FileChannelImpl.c, сначала он вызывает CreateFileMapping, а затем вызывает MapViewOfFile. Если посмотреть на этот источник, если вы попросите отобразить весь файл в памяти, этот метод будет работать именно так, как вы просите. Цитировать MSDN:

Отображение файла делает указанную часть файла видимой в адресное пространство вызывающего процесса.

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

Как OP вызывает карту, «указанная часть» файла - это весь файл. Это не будет способствовать исчерпанию heap , но может способствовать исчерпанию виртуального адресного пространства, которое все еще является ошибкой OOM. Это может убить ваше приложение так же тщательно, как и исчерпание кучи.

Наконец, когда вы делаете Matcher, Matcher потенциально делает больше копий этих 600 МБ CharBuffer, в зависимости от того, как вы его используете. Уч. Это много памяти, используемой небольшим количеством объектов! Учитывая Matcher, каждый раз, когда вы звоните toMatchResult(), вы сделаете String копию всего CharBuffer. Кроме того, каждый раз, когда вы звоните replaceAll(), в лучшем случае вы сделаете String копию всего CharBuffer. В худшем случае вы сделаете StringBuffer, который будет медленно расширяться до полного размера replaceAll результата (применяя большую нагрузку на память кучи), а затем сделаете String из этого.

Таким образом, если вы наберете replaceAll для Matcher для файла размером 300 МБ, и ваше совпадение будет найдено, то вы сначала сделаете серию все более крупных StringBuffer с, пока не получите тот, который 600 МБ. Затем вы сделаете String копию этого StringBuffer. Это может быстро и легко привести к истощению кучи.

Вот суть: Matcher s не оптимизированы для работы с очень большими буферами. Вы можете очень легко и без планирования сделать несколько очень больших объектов. Я обнаружил это, когда делал нечто достаточно похожее на то, что вы делаете, и сталкивался с истощением памяти, а затем просматривал исходный код Matcher.

ПРИМЕЧАНИЕ: нет звонка unmap. Как только вы вызываете map, виртуальное адресное пространство вне кучи, связанной с MappedByteBuffer, застревает там до тех пор, пока MappedByteBuffer не соберет мусор. В результате вы не сможете выполнять определенные операции с файлом (удалять, переименовывать, ...) до тех пор, пока MappedByteBuffer не будет собран сборщиком мусора. Если для разных файлов вызывать карту достаточно раз, но в куче недостаточно памяти, чтобы вызвать сборку мусора, вы можете выйти из памяти вне кучи. Для обсуждения см. Ошибка 4724038 .

В результате всего вышеприведенного обсуждения, если вы будете использовать его для создания Matcher для больших файлов, и вы будете использовать replaceAll на Matcher, тогда ввод-вывод с отображением в память вероятно, не путь. Он просто создаст слишком много больших объектов в куче, а также израсходует много вашего виртуального адресного пространства вне кучи. В 32-битной Windows у вас есть только 2 ГБ (или, если вы изменили настройки, 3 ГБ) виртуального адресного пространства для JVM, и это приведет к значительному давлению памяти как внутри, так и снаружи кучи.

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

2 голосов
/ 12 июня 2009

Ваш шаблон поиска соответствует более чем одной строке? Если нет, то самое простое решение - читать построчно :). Просто действительно

Но если шаблон поиска совпадает с несколькими строками, вам необходимо сообщить нам об этом, поскольку поиск построчно не будет работать.

0 голосов
/ 04 февраля 2013

Использование буфера для чтения большого количества файлов один раз Есть одна хитрость: каждый раз, когда вы читаете новую строку в буфер, убедитесь, что она имеет перекрытие длины l, которая является длиной подстроки l = длина (подстрока); в то время как (не eof) делают начать если найти (буфер, подстрока) вернуть TRUE;
буфер [0..l] = подстрока; буфер [l + 1, конец] = read_new_chars_intobuffer; конец

0 голосов
/ 23 июня 2010

В моем случае добавление -Djava.compiler=NONE после classpath может решить проблему.

0 голосов
/ 12 июня 2009

Заявления о том, что FileChannel.map загрузит весь файл в память, являются ошибочными, ссылаясь на MappedByteBuffer , который возвращает FileChannel.map (). Это 'прямой байтовый буфер', он не исчерпывает вашу память (прямые байтовые буферы используют подсистему виртуальной памяти ОС для перелистывания данных в память и из памяти по мере необходимости, что позволяет адресовать гораздо большие куски памяти, какими они были физическая оперативная память.) Но опять же, одна МББ будет работать только для файлов размером ~ 2 ГБ.

Попробуйте это:

FileChannel fc = new FileInputStream(fFile).getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());

CharBuffer chrBuff = mbb.asCharBuffer();

Он не будет загружать весь файл в память, и chrBuff является только представлением резервного MappedByteBuffer, а не копией.

Хотя я не уверен, как справиться с декодированием.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...