Java: прочитать последние n строк ОГРОМНОГО файла
Я хочу прочитать последние n строк очень большого файла, не читая весь файл в какую-либо область буфера/памяти, используя Java.
Я просмотрел API-интерфейсы JDK и ввод-вывод Apache Commons и не смог найти тот, который подходит для этой цели.
Я думал о том, как хвост или меньше делают это в UNIX. Я не думаю, что они загружают весь файл, а затем показывают последние несколько строк файла. Должен быть аналогичный способ сделать то же самое и в Java.
11 ответы
Если вы используете RandomAccessFile , вы можете использовать length и seek чтобы перейти к определенной точке ближе к концу файла, а затем читать оттуда вперед.
Если вы обнаружите, что строк недостаточно, вернитесь с этой точки и повторите попытку. Как только вы выяснили, где находится N начинается последняя строка, вы можете искать там и просто читать и печатать.
На основе ваших свойств данных можно сделать первоначальное предположение о наилучшем предположении. Например, если это текстовый файл, возможно, длина строки не будет превышать в среднем 132, поэтому, чтобы получить последние пять строк, начните за 660 символов до конца. Затем, если вы ошиблись, попробуйте еще раз на 1320 (вы даже можете использовать то, что вы узнали из последних 660 символов, чтобы исправить это — например: если эти 660 символов были всего тремя строками, следующая попытка может быть 660 / 3 * 5, плюс, может быть, немного больше на всякий случай).
Я нашел это самым простым способом сделать, используя ReversedLinesFileReader от Apache Commons-io API Этот метод даст вам строку снизу вверх файла, и вы можете указать n_lines значение для указания номера строки.
import org.apache.commons.io.input.ReversedLinesFileReader; File file = new File("D:\\file_name.xml"); int n_lines = 10; int counter = 0; ReversedLinesFileReader object = new ReversedLinesFileReader(file); while(counter
Внимание: каждый раз, когда вы звоните readLine() , курсор продвигается вперед. Таким образом, этот код фактически пропустил бы каждую вторую строку, потому что вывод из readLine() в while выписка не фиксируется. — аапирс
Этот код немного ошибочен, потому что readLine() вызывается дважды. как упоминал aapierce. Но полный указывает на ReversedLinesFileReader — Винксарма
@aapierce Комментарии от вас и винкшармы устарели, верно? Я думаю, что редактирование Mise решило проблему. Это немного сбивает с толку, когда комментарии не соответствуют текущей версии самого поста. — Даниэль Айзенрайх
@DanielEisenreich Да, похоже, ответ был отредактирован с тех пор, как я добавил свой комментарий 3 года назад. Мне не очевидно, как теперь редактировать свой комментарий. Извини! — аапирс
RandomAccessFile — хорошее место для начала, как описано в других ответах. Существует одна важное предостережение хотя.
Если ваш файл не закодирован с кодировкой один байт на символ, readLine() метод не будет работать для вас. И readUTF() не будет работать ни при каких обстоятельствах. (Он читает строку, которой предшествует количество символов. )
Вместо этого вам нужно убедиться, что вы ищете маркеры конца строки таким образом, чтобы соблюдать границы символов кодировки. Для кодировок с фиксированной длиной (например, вариантов UTF-16 или UTF-32) вам необходимо извлекать символы, начиная с байтовых позиций, кратных размеру символа в байтах. Для кодировок переменной длины (например, UTF-8) вам нужно искать байт, который должен быть первым байтом символа.
В случае UTF-8 первый байт символа будет 0xxxxxxx or 110xxxxx or 1110xxxx or 11110xxx . Все остальное — либо второй/третий байт, либо недопустимая последовательность UTF-8. Видеть Стандарт Unicode, версия 5.2, глава 3.9, Таблица 3-7. Это означает, как указано в обсуждении комментариев, что любые байты 0x0A и 0x0D в правильно закодированном потоке UTF-8 будут представлять символ LF или CR. Таким образом, простой подсчет байтов 0x0A и 0x0D является допустимой стратегией реализации (для UTF-8), если мы можем предположить, что другие виды разделителей строк Unicode (0x2028, 0x2029 и 0x0085) не используются. Нельзя так предполагать, тогда код был бы сложнее.
Определив правильную границу символа, вы можете просто вызвать new String(. ) передавая массив байтов, смещение, количество и кодировку, а затем повторно вызывая String.lastIndexOf(. ) для подсчета концов строк.
+1 за упоминание предостережения. Я думаю, что для UTF-8 проблема может быть упрощена путем сканирования ‘\n’. По крайней мере, это то, что Джон Скит, кажется, подразумевает в своем ответе на вопрос. связанный вопрос. Кажется, ‘\n’ может встречаться только как допустимый символ в UTF-8 и никогда в «дополнительных байтах». — Стейн де Витт
Да, для UTF-8 это просто. UTF-8 кодирует символы либо как один байт (все символы ASCII), либо как несколько байтов (все остальные символы Unicode). К счастью для нас, новая строка — это символ ASCII, а в UTF-8 ни один многобайтовый символ не содержит байтов, которые также являются допустимыми символами ASCII. То есть, если вы сканируете массив байтов в поисках новой строки ASCII и находите ее, вы знать это новая строка, а не часть какого-либо другого многобайтового символа. я написал блоге у которого есть хорошая таблица, иллюстрирующая это. — Стейн де Витт
Проблема в 1) кодировках символов, где байт 0x0a не является новой строкой (например, UTF-16) и 2) тот факт, что существуют другие кодовые точки разделителя строк Unicode; например 0x2028 , 0x2029 и 0x0085 — Стивен С
Да, простой сценарий справедлив только для UTF-8 и когда новые строки кодируются как CRLF или просто LF. Однако я думаю, что на практике это охватывает большинство реальных сценариев. UTF-16 встречается довольно редко, когда речь идет о кодировке текстовых файлов (она часто используется в памяти, но не очень часто в файлах), и я не знаю многих редакторов, которые будут вставлять эти другие разделители строк Unicode. — Стейн де Витт
Наблюдения и советы этой статьи мы подготовили на основании опыта команды ReversedLinesFileReader можно найти в Apache Commons IO java-библиотека.
int n_lines = 1000; ReversedLinesFileReader object = new ReversedLinesFileReader(new File(path)); String result=""; for(int i=0;i return result;
ответ дан 21 мар ’19, в 14:03
Считывание из файла последней строки
Надо определить последнюю строчку, и взять из нее значение 7348537A889PB5, файл всегда обновляется строки добавляются в конец файла.
Считывание строки из файла до точки
Доброго времени суток 🙂 Вопрос вот в чем, у меня есть файл с текстом. Нужно считывать текст и.
Считывание файла в массив без последней строки
Интересует вопрос, как считывать txt в list БЕЗ последней строки. Мой код который записывает txt.
Считывание последней строки со спец-символом ‘\r’ из текстового файла
Всем привет! Проблема состояла в том, что моя программа неправильно считывала последнее число в.
Функция: сравнение первой строки первого текстового файла и последней строки второго файла
Даны два текстовый файла, состоящие из некоторого количества строк. Написать функцию для сравнения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public boolean isValidFile(String filename){ try { FileInputStream fis = new FileInputStream(filename); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); String str1; String str2; while ((str1 = br.readLine()) != null) { str2 = str1; } if (str1.contains("some regexp")){ return true; }else{ return false; } } catch (IOException e) { log.error("IOException occured",e); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
private String getLastLine(final File aFile) { String line = null; String tmp = null; BufferedReader in = null; try { in = new BufferedReader(new FileReader(aFile)); while ((tmp = in.readLine()) != null) { line = tmp; } } catch (FileNotFoundException exception) { LOGGER.error("", exception); } catch (IOException exception) { LOGGER.error("", exception); } finally { IOUtils.closeQuietly(in); } return line; }
На файле в 150Мбайт оба варианта мучаются около секунды, т.к. перебирают весь файл.
На базе RandomAccessFile последняя строка из того же файла получается за пару миллисекунд.
1 2 3 4 5 6 7 8 9 10 11 12 13
private static String ReadLastLine(File file) throws FileNotFoundException, IOException String result; try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { result = null; long startIdx = file.length(); while (result == null } return result; }
Сообщение от kotelok
private static String ReadLastLine(File file) throws FileNotFoundException, IOException RandomAccessFile raf = new RandomAccessFile(file, "r"); String result = null; long startIdx = file.length(); while (result == null return result; }
Если честно, то я про RandomAccessFile сам узнал только что, попытавшись решить эту задачу.
http://docs.oracle.com/javase/. sFile.html
На практике оказалось, что он реализует достаточно эффективный доступ доступ к файлам по позиции. По крайней мере в сравнении с последовательным перебором всех строк.
По коду — встаём в самый конец файла и пытаемся считать две строки. Если вторая считалась успешно (не null и не пустая), то значит она и есть последняя строка файла. Если не считалась, то повторяем попытку с позиции на единицу меньше, чем конец файла. И так пока не достигнем успеха.
P.S.: но там кривовато получилось. Если в файле всего одна строка или вообще строк нет, то будет беда. Сейчас доработаю.
Быстро прочитать последнюю строку текстового файла?
Какой самый быстрый и эффективный способ чтения последней строки текста из [очень, очень большого] файла в Java?
11 ответов
Посмотрите на мой ответ на аналогичный вопрос для C#. Код был бы очень похож, хотя поддержка кодирования в Java несколько иная.
В общем, это не очень легко сделать в целом. Как указывает MSalter, UTF-8 позволяет легко обнаружить \r или же \n поскольку представление этих символов в UTF-8 точно такое же, как в ASCII, и эти байты не будут иметь многобайтовый символ.
Таким образом, в основном, возьмите буфер (скажем) 2 КБ и постепенно читайте назад (перейдите к 2 КБ, прежде чем вы были раньше, прочитайте следующие 2 КБ), проверяя завершение строки. Затем перейдите в нужное место в потоке, создайте InputStreamReader на вершине, и BufferedReader более того. Тогда просто позвоните BufferedReader.readLine() ,
Ниже приведены две функции, одна из которых возвращает последнюю непустую строку файла без загрузки или пошагового выполнения по всему файлу, а другая возвращает последние N строк файла без пошагового выполнения по всему файлу:
Хвост выполняет масштабирование до последнего символа файла, затем шаг за шагом, символ за символом, записывает то, что видит, пока не обнаружит разрыв строки. Как только он находит разрыв строки, он выходит из цикла. Переворачивает то, что было записано, бросает его в строку и возвращает. 0xA — это новая строка, а 0xD — возврат каретки.
Если ваши окончания строки \r\n или же crlf или какой-то другой «двойной перевод новой строки в стиле новой строки», тогда вам нужно будет указать n*2 строки, чтобы получить последние n строк, потому что он считает 2 строки для каждой строки.
public String tail( File file ) < RandomAccessFile fileHandler = null; try < fileHandler = new RandomAccessFile( file, "r" ); long fileLength = fileHandler.length() - 1; StringBuilder sb = new StringBuilder(); for(long filePointer = fileLength; filePointer != -1; filePointer--)< fileHandler.seek( filePointer ); int readByte = fileHandler.readByte(); if( readByte == 0xA ) < if( filePointer == fileLength ) < continue; >break; > else if( readByte == 0xD ) < if( filePointer == fileLength - 1 ) < continue; >break; > sb.append( ( char ) readByte ); > String lastLine = sb.reverse().toString(); return lastLine; > catch( java.io.FileNotFoundException e ) < e.printStackTrace(); return null; >catch( java.io.IOException e ) < e.printStackTrace(); return null; >finally < if (fileHandler != null ) try < fileHandler.close(); >catch (IOException e) < /* ignore */ >> >
Но вам, вероятно, не нужна последняя строка, вам нужны последние N строк, поэтому используйте это вместо:
public String tail2( File file, int lines) < java.io.RandomAccessFile fileHandler = null; try < fileHandler = new java.io.RandomAccessFile( file, "r" ); long fileLength = fileHandler.length() - 1; StringBuilder sb = new StringBuilder(); int line = 0; for(long filePointer = fileLength; filePointer != -1; filePointer--)< fileHandler.seek( filePointer ); int readByte = fileHandler.readByte(); if( readByte == 0xA ) < if (filePointer < fileLength) < line = line + 1; >> else if( readByte == 0xD ) < if (filePointer < fileLength-1) < line = line + 1; >> if (line >= lines) < break; >sb.append( ( char ) readByte ); > String lastLine = sb.reverse().toString(); return lastLine; > catch( java.io.FileNotFoundException e ) < e.printStackTrace(); return null; >catch( java.io.IOException e ) < e.printStackTrace(); return null; >finally < if (fileHandler != null ) try < fileHandler.close(); >catch (IOException e) < >> >
Вызовите вышеупомянутые методы как это:
File file = new File("D:\\stuff\\huge.log"); System.out.println(tail(file)); System.out.println(tail2(file, 10));
Предупреждение На диком западе юникода этот код может привести к неправильному выводу этой функции. Например, «Мэри?» Вместо «Мэри». Символы с шляпами, акцентами, китайскими и т. Д. Могут привести к неправильному выводу, потому что акценты добавляются в качестве модификаторов после символа. Реверсирование составных символов меняет характер личности персонажа при обращении. Вам нужно будет выполнить полный набор тестов на всех языках, с которыми вы планируете его использовать.