Как считать последнюю строку в файле java

Обновлено: 05.07.2024

Есть большой текстовый файл (40 МБ). Прежде, чем его обрабатывать, мне надо узнать одно значение, которое записано в последней строке этого файла.

Как я могу прочитать последнюю строку в файле, не загружая в память весь файл?

Сейчас я делаю это вот так:

Это приводит к OutOfMemoryError внутри FileUtils при больших файлах.

Вопрос: Как можно по-другому читать последнюю строку файла, чтобы ошибки не было (кроме ограничения памяти с помощью -mx128M) ?

Здравствуйте, Дмитрий Писаренко, Вы писали:

ДП>Есть большой текстовый файл (40 МБ). Прежде, чем его обрабатывать, мне надо узнать одно значение, которое записано в последней строке этого файла.
Текстовые строки имеют ограничение на длину?

ДП>Как я могу прочитать последнюю строку в файле, не загружая в память весь файл?
RandomAccessFile, прикидываешь где будет находится строка, и с того места сканируешь на предмет символов новый строки и конца файла.
С другой стороны можно так же и через FileInputStream.skip() прыгнуьт в конце файла и сканировать дальше на предмет строк в конце файла.

ДП> lines = (List<String>) FileUtils.readLines(aFile);
ДП>Это приводит к OutOfMemoryError внутри FileUtils при больших файлах.
Из пушки по воробьям.

Здравствуйте, Дмитрий Писаренко, Вы писали:

ДП>Вопрос: Как можно по-другому читать последнюю строку файла, чтобы ошибки не было (кроме ограничения памяти с помощью -mx128M) ?

Здравствуйте, Zelgadis, Вы писали:

только не str1, а str2 в конце нужно проверять.

Здравствуйте, Zelgadis, Вы писали:

Z>К примеру так.
Z> while ((str1 = br.readLine()) != null) <
Ты пошутил или на полном серьезе предлагаешь вычитывать 40Мб данных, и преобразовывать их в строки только для того чтобы получить одну последнюю строку? Здравствуйте, Blazkowicz, Вы писали:

B>Ты пошутил или на полном серьезе предлагаешь вычитывать 40Мб данных, и преобразовывать их в строки только для того чтобы получить одну последнюю строку?
Конечно решение не очень хорошее, но хотя бы не приведёт к OutOfMemory как изначальное. Всё таки не 40 mb в память загружаем =).
А так конечно был не прав, сори. Здравствуйте, Zelgadis, Вы писали:

Z>Конечно решение не очень хорошее, но хотя бы не приведёт к OutOfMemory как изначальное. Всё таки не 40 mb в память загружаем =).
Конечно не 40, а больше. Твое решение от оригинального отличается только тем что не хранит ссылки на строки, таким образом GC их будет собирать понемногу.

Здравствуйте, Blazkowicz, Вы писали:

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

Можно блоками считывать от конца файла к началу и искать в этих блоках конец строки.
Ещё можно через FileChannel.map() отмапить в буфер и сканировать его от конца.

Всем спасибо за помощь!

Кроме предложенных здесь вариантов, есть ещё один, не очень элегантный, зато простой:

Здравствуйте, Дмитрий Писаренко, Вы писали:

ДП>Кроме предложенных здесь вариантов, есть ещё один, не очень элегантный, зато простой:
ДП>


Это простой и корявый вариант. Все 40Мб будут таки загружены в память.

Здравствуйте, Zelgadis, Вы писали:

Z>Здравствуйте, Blazkowicz, Вы писали:

B>>Ты пошутил или на полном серьезе предлагаешь вычитывать 40Мб данных, и преобразовывать их в строки только для того чтобы получить одну последнюю строку?
Z>Конечно решение не очень хорошее, но хотя бы не приведёт к OutOfMemory как изначальное. Всё таки не 40 mb в память загружаем =).

Если в файле 2 строки, первая

40МБ, а вторая небольшая, то приведет влегкую. Но это уже так, придирки

Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, Zelgadis, Вы писали:

Z>>К примеру так.
Z>> while ((str1 = br.readLine()) != null) <
B>Ты пошутил или на полном серьезе предлагаешь вычитывать 40Мб данных, и преобразовывать их в строки только для того чтобы получить одну последнюю строку?

Я хочу прочитать последние n строк очень большого файла, не читая весь файл в любой буфер/область памяти с помощью Java.

Я просмотрел API JDK и Apache Commons I/O и не смог найти подходящий для этой цели.

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

Если вы используете RandomAccessFile , вы можете использовать length и seek , чтобы перейти к определенной точке ближе к концу файла, а затем прочитать оттуда.

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

Исходное предположение наилучшего предположения может быть сделано на основе ваших свойств данных. Например, если это текстовый файл, возможно, длина строк не будет превышать в среднем 132, поэтому, чтобы получить последние пять строк, запустите 660 символов до конца. Затем, если вы ошиблись, попробуйте еще раз в 1320 (вы можете использовать то, что вы узнали из последних 660 символов, чтобы настроить это: например: если эти 660 символов были всего тремя строками, следующая попытка может быть 660/3 * 5, плюс, может быть, немного больше на всякий случай).

Я нашел это самым простым способом, используя ReversedLinesFileReader из apache commons-io api, Этот метод даст вам строку снизу вверх, а вы можете указать n_lines значение, чтобы указать количество строк.

Внимание: каждый раз, когда вы вызываете readLine() , курсор перемещается. Таким образом, этот код фактически пропустил бы все остальные строки, потому что выходные данные readLine() в операторе while не фиксируются. Мне просто интересно, если этот метод эффективен или нет? Этот код немного неисправен, потому что readLine () вызывается дважды. как упомянуто Aapierce. Но полные очки ReversedLinesFileReader @DanielEisenreich Да, похоже, что ответ был отредактирован, так как я добавил свой комментарий 3 года назад. Для меня не очевидно, как редактировать мой комментарий сейчас. Сожалею!

RandomAccessFile - это хорошее место для начала, как описано в других ответах. Однако существует одна важная оговорка.

Если ваш файл не закодирован с кодировкой с одним байтом на символ, метод readLine() не будет работать для тебя. И readUTF() не будет работать ни при каких обстоятельствах. (Он читает строку, которой предшествует знак символа. )

Вместо этого вам нужно убедиться, что вы ищете маркеры конца строки таким образом, чтобы соблюдались границы символов кодирования. Для кодировок с фиксированной длиной (например, вкусов UTF-16 или UTF-32) вам необходимо извлечь символы, начиная с позиций байтов, которые делятся на размер символа в байтах. Для кодировок с переменной длиной (например, UTF-8) вам необходимо выполнить поиск байта, который должен быть первым байтом символа.

В случае UTF-8 первый байт символа будет 0xxxxxxx или 110xxxxx или 1110xxxx или 11110xxx . Все остальное является либо вторым/третьим байтом, либо незаконной последовательностью UTF-8. См. Стандарт Unicode, версия 5.2, глава 3.9, таблица 3-7. Это означает, что, как указывает обсуждение комментариев, любые байты 0x0A и 0x0D в правильно закодированном потоке UTF-8 будут представлять символ LF или CR. Таким образом, подсчет байтов является допустимой стратегией реализации (для UTF-8).

Я хочу прочитать последние n строк очень большого файла, не читая весь файл в любой буфер / область памяти с помощью Java.

Я осмотрел API JDK и Apache Commons I / O и не смог найти тот, который подходит для этой цели.

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

если вы используете RandomAccessFile , вы можете использовать length и seek чтобы попасть в конкретную точку в конце файла, а затем читать дальше.

если вы обнаружите, что было недостаточно линий, отступите с этой точки и повторите попытку. Как только вы выяснили, где N - Я последняя строка начинается, вы можете искать там и просто читать и печатать.

первоначальное предположение наилучшего предположения может быть сделано на основе ваших данных свойства. Например, если это текстовый файл, возможно, длина строки не будет превышать в среднем 132, поэтому, чтобы получить последние пять строк, начните 660 символов до конца. Затем, если вы ошиблись, попробуйте еще раз в 1320 (вы даже можете использовать то, что вы узнали из последних 660 символов, чтобы настроить это-пример: если эти 660 символов были всего три строки, следующая попытка может быть 660 / 3 * 5, плюс, может быть, немного больше на всякий случай).

Я нашел самый простой способ сделать с помощью ReversedLinesFileReader С Apache commons-io API-интерфейс. Этот метод даст вам строку снизу вверх файла, и вы можете указать n_lines значение для указания количества линий.

RandomAccessFile-хорошее место для начала, как описано в других ответах. Есть один важный нюанс хотя.

если ваш файл не закодирован с кодировкой один байт на символ, readLine() метод не будет работать для вас. И readUTF() не будет работать ни при каких обстоятельствах. (Он читает строку, которой предшествует число символов . )

вместо этого вам нужно будет убедиться, что вы ищете маркеры конца строки таким образом, чтобы уважать границы символов кодировки. Для кодировок фиксированной длины (например, вкусы UTF-16 или UTF-32) необходимо извлечь символы, начиная с байтовых позиций, которые делятся на размер символов в байтах. Для кодировок переменной длины (например, UTF-8) вам нужно найти байт, который должны быть первым байтом символа.

в случае UTF-8 первым байтом символа будет 0xxxxxxx или 110xxxxx или 1110xxxx или 11110xxx . Все остальное - либо второй / третий байт, либо незаконная последовательность UTF-8. См.Стандарт Unicode, Версия 5.2, Глава 3.9, таблица 3-7. Это означает, как указывает обсуждение комментариев, что любые байты 0x0A и 0x0D в правильно закодированном потоке UTF-8 будут представлять символ LF или CR. Таким образом, подсчет байтов является допустимой стратегией реализации (для UTF-8).

определив правильную границу символа, вы можете просто позвонить new String(. ) передача массива байтов, смещение, количество и кодирование, а затем повторно вызовите String.lastIndexOf(. ) для подсчета конца строк.

обратите внимание, что в Apache Commons Collections 4 Этот класс, похоже, был переименован в CircularFifoQueue

У меня была аналогичная проблема, но я не понял других решений.

я использовал это. Надеюсь, это простой код.

вот лучший способ я нашел, чтобы сделать это. Простой и довольно быстрый и оперативную память.

В этом уроке мы будем использовать методы Files.lines (), Files.ReadString (), Files.ReadAllBytes() и классы FileReader, BufferedReader и Scanner для чтения файла в строку на Java с примерами.

Вступление

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

Вот список всех классов и методов, которые мы рассмотрим:

  • Файлы.строки()
  • Файлы.Строка чтения()
  • Файлы.ReadAllBytes()
  • Устройство для чтения файлов
  • Буферизатор
  • Сканер

Файлы.строки()

Класс Files содержит статические методы для работы с файлами и каталогами. Полезным методом является lines () , который возвращает поток строк: Поток<Строка> . Из этого потока можно получить строки, содержащиеся в файле.

Метод принимает Путь к файлу, который мы хотели бы прочитать, с необязательной кодировкой . Мы будем использовать синтаксис try-with-resources для автоматизации очистки и закрытия:

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

Вместо печати каждой строки для добавления строк можно использовать StringBuilder :

С помощью StringBuilder весь файл может быть представлен в одной Строке (переменная содержимое выше). Перед выполнением таких итераций важно учитывать длину входного файла.

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

Файлы.Строка чтения()

Начиная с Java 11, класс Files познакомил нас с методом ReadString () , который принимает Путь к файлу, а также Кодировку .

В отличие от Files.lines() , он возвращает Строку напрямую, а не Поток объект:

Файлы.ReadAllBytes()

Этот метод также принимает Путь к файлу, который мы хотели бы прочитать:

Теперь массив байт содержит всю информацию из input.txt файл. Самый простой способ преобразовать его в строку-поместить их в конструктор с необязательной кодировкой :

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

Сканер

Поскольку мы работаем со строками, мы хотели бы использовать методы, возвращающие строки. Сканер имеет next() и nextLine() именно для этого. Оба метода возвращают объекты типа String . Первый используется для чтения произвольных строк, в то время как второй анализирует и возвращает целые строки.

Если каждая строка содержит нужное количество данных, то следующая строка() является идеальным выбором. Если в файле есть важная информация, которая разбита на более мелкие фрагменты, но не обязательно на строки (или файл содержит, скажем, одну строку), то next() может быть лучшим вариантом.

Мы используем цикл while до тех пор, пока sc содержит больше элементов. Если бы мы не проверили с помощью hasNext() , sc выдал бы Исключение NoSuchElementException , если мы попытаемся получить доступ к элементу после последнего.

Идея использования методов hasNext() и next() исходит из интерфейса Итератора , поскольку Сканер реализует его внутренне.

Устройство для чтения файлов

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Файл FileReader используется для чтения файлов. Он предлагает методы read() и read(char []) , которые возвращают один символ и несколько символов соответственно. Кроме того, он принимает Файл или Строку в конструктор.

Считыватель файлов.чтение(символ[])

Давайте откроем файл с помощью FileReader и прочитаем его содержимое:

Метод read() принимает последовательность символов (в которой мы храним прочитанные символы), начальную и конечную точки того, что мы хотели бы прочитать. В частности, мы решили прочитать не более 256 символов. Если input.txt имеет больше, мы будем читать только 256 символов. Если в нем меньше, возвращаются читаемые символы.

Возвращаемое значение, хранящееся внутри целого числа n , может быть использовано для проверки того, сколько символов метод на самом деле прочитал. В случае, если достигнут конец потока, метод возвращает -1 .

Поскольку метод заполняет символ[] , мы можем преобразовать его в Строку . Аналогичный результат можно получить с помощью String.valueOf(char[]) .

Читатель файлов.чтение()

Метод read() без char[] считывает по одному символу за раз. Мы захотим просмотреть содержимое и прочитать каждый символ самостоятельно:

Здесь мы проверяем, не является ли прочитанный символ -1 , что указывало на то, что для чтения больше не осталось символов. Если нет, мы добавим() его в StringBuilder и, наконец, преобразуем его в Строку .

Примечание: Оба read() и read(char[]) считанные байты, преобразуйте их в символы и возвращайте их по одному . Это неэффективно и должно выполняться с помощью буферизации , когда это возможно.

Буферизатор

Рекомендуется обернуть любой потенциально дорогостоящий считыватель в BufferedReader для повышения производительности, поскольку буферизация символов обеспечивает более эффективное чтение входного текста.

Давайте создадим экземпляр BufferedReader :

На данный момент у нас есть объект bufferedreader, готовый для чтения содержимого из input.txt . В этом примере мы будем читать файл построчно, хотя BufferedReader поддерживает чтение отдельных символов по отдельности, а также нескольких символов в массив.

Давайте используем этот экземпляр BufferedReader для чтения файла и сохранения его содержимого, строка за строкой, в строку:

Еще раз, мы используем StringBuilder для сбора всех строк. Чтобы разделить каждую строку, мы добавляем нулевой терминатор ( \n ) между ними. Наконец, мы закрываем ручей.

Вывод

В этой статье мы рассмотрели некоторые распространенные методы чтения файлов в строки на Java. Существует множество вариантов, но большинство из них имеют схожий основной принцип: укажите путь к файлу, прочитайте содержимое в структуру данных (например, char[] или строку); затем выполните некоторую окончательную обработку, чтобы собрать все содержимое файла соответствующим образом.

Мне нужно читать большой текстовый файл размером около 5-6 ГБ построчно, используя Java.

Как я могу сделать это быстро?

@kamaci et. и др. Этот вопрос не должен быть помечен как дубликат. «Быстро прочитать последнюю строчку» не является альтернативой, и ее спорно ли «быстрый способ чтения текстового файла строка за строкой» есть. Самый быстрый способ сделать что-то не обязательно является обычным способом. Кроме того, ответы ниже включают код, а наиболее подходящая альтернатива, которую вы перечисляете, - нет. Этот вопрос полезен. В настоящее время это лучший результат поиска в Google по запросу "java read file by line". И, наконец, его устранение приводит к переполнению стека и обнаружению, что 1 в каждом 2 вопросе помечается для удаления. Несмотря на то, что я читал комментарии, утверждая, что близкая политика SO - отстой, SO остается в этом. Это такая ограниченная перспектива для разработчиков, чтобы хотеть избежать избыточности любой ценой! Просто пусть это будет! Крем поднимется до самого верха, а дерьмо само по себе опустится до самого дна. Несмотря на то, что вопрос, возможно, задавался ранее (какой вопрос не является ??), это не означает, что новый вопрос не сможет лучше его сформулировать, получить лучшие ответы, повысить рейтинг в поисковых системах и т. Д. Интересно, что это вопрос теперь «защищен» . Просто невероятно, как вопросы помечаются как дубликаты, просто читая заголовок.

Распространенным примером является использование

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

РЕДАКТИРОВАТЬ: менее распространенный шаблон, который позволяет избежать line утечки.

ОБНОВЛЕНИЕ: в Java 8 вы можете сделать

Как выглядит этот шаблон при правильной обработке исключений? Я отмечаю, что br.close () генерирует IOException, что кажется удивительным - что может произойти при закрытии файла, открытого для чтения, в любом случае? Конструктор FileReader может вызвать исключение FileNotFound. Если у меня есть файл 200 МБ, и он может читать со скоростью 90 МБ / с, то я ожидаю, что это займет

3 с? Похоже, мое занятие заняло несколько минут с этим «медленным» способом чтения Я на SSD, поэтому скорость чтения не должна быть проблемой?

@JiewMeng Так что я подозреваю, что что-то еще, что вы делаете, требует времени. Можете ли вы попробовать просто прочитать строки файла и ничего больше. Почему бы и нет for(String line = br.readLine(); line != null; line = br.readLine()) , в Java 8 вы можете сделать, try( Stream<String> lines = Files.lines(. ) ) < for( String line : (Iterable<String>) lines::iterator ) < . >> что трудно не ненавидеть. @AleksandrDubinsky Проблема с замыканиями в Java 8 заключается в том, что она очень легко делает код более сложным для чтения (а также более медленным). Я вижу, что многие разработчики злоупотребляют им, потому что это «круто».

Посмотрите на этот блог:

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

Мой файл 1,5 гигабайт, и невозможно прочитать файл, используя ваш ответ! @AboozarRajabi Конечно, это возможно. Этот код может читать любой текстовый файл. Отказался за некачественную ссылку. Тут совершенно бессмысленно DataInputStream , а не тот поток закрыт. Ничего плохого в Java Tutorial, и нет необходимости цитировать произвольный сторонний мусор в Интернете, как этот. Я бы отказался от комментариев, у вас есть 4 строки 100% избыточных комментариев для 6 строк кода.

После выхода Java 8 (март 2014 года) вы сможете использовать потоки:

Печать всех строк в файле:

Используйте StandardCharsets.UTF_8 , используйте Stream<String> для краткости, и избегайте использования, forEach() и особенно forEachOrdered() если нет причины. Если бы я использовал forEach вместо forEachOrdered, строки могли бы быть напечатаны не по порядку, не так ли? @msayag, ты прав, нужно forEachOrdered для того чтобы выполнить по порядку. Имейте в виду, что в этом случае вы не сможете распараллелить поток, хотя я обнаружил, что распараллеливание не включается, если в файле нет тысяч строк.

Вот пример с полной обработкой ошибок и поддержкой спецификации кодировки для pre-Java 7. В Java 7 вы можете использовать синтаксис try-with-resources, который делает код чище.

Если вы просто хотите использовать кодировку по умолчанию, вы можете пропустить InputStream и использовать FileReader.

Вот Groovy-версия с полной обработкой ошибок:

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

В Java 8 вы можете сделать:

Некоторые примечания: поток, возвращаемый Files.lines (в отличие от большинства потоков), должен быть закрыт. По причинам, указанным здесь, я избегаю использования forEach() . Странный код (Iterable<String>) lines::iterator переводит поток в Iterable.

Отказ от реализации Iterable этого кода определенно уродлив, хотя и полезен. Это нуждается в броске (то есть (Iterable<String>) ), чтобы работать. Как я могу пропустить первую строку с этим методом? @qed for(String line : (Iterable<String>) lines.skip(1)::iterator) Если вы не собираетесь на самом деле использовать Stream функции, используя Files.newBufferedReader вместо Files.lines и повторяющегося вызова , readLine() пока null вместо того , чтобы использовать конструкции , как , (Iterable<String>) lines::iterator кажется, гораздо проще . Почему вы используете :: in lines :: iterator? Единственное использование, которое я знаю для :: - это упаковывать имя метода в лямбда-функцию. В течение параметра цикла после того, как : должен быть переменной , а вы получите некоторый метод лямбда с помощью ::

Что вы можете сделать, это отсканировать весь текст с помощью сканера и проходить текст построчно. Конечно, вы должны импортировать следующее:

Сканер в основном сканирует весь текст. Цикл while используется для перемещения по всему тексту.

.hasNextLine() Функция булева , которая возвращает истину , если есть еще несколько строк в тексте. .nextLine() Функция дает Вам всю строку в виде строки , которые вы можете использовать, как вы хотите. Пытаться System.out.println(line) напечатать текст.

Примечание: .txt - это текст типа файла.

Должно ли объявление метода выглядеть вместо этого: «public static void readText throws FileNotFoundException () <´ Like:« public static void readText () throws FileNotFoundException <´ Это значительно медленнее, чем он BufferedReader.readLine() , и он попросил самый эффективный метод.

FileReader не позволит вам указать кодировку, используйте InputStreamReader вместо этого, если вам нужно указать ее:

Если вы импортировали этот файл из Windows, он может иметь кодировку ANSI (Cp1252), поэтому вам необходимо указать кодировку.

Я задокументировал и протестировал 10 различных способов чтения файлов на Java, а затем сопоставил их друг с другом, заставив их читать в тестовых файлах от 1 КБ до 1 ГБ. Вот самые быстрые 3 метода чтения файлов для чтения тестового файла объемом 1 ГБ.

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

Протестировано в Java 7, 8, 9. В целом это был самый быстрый метод. Чтение файла объемом 1 ГБ всегда было менее 1 секунды.

Это было успешно протестировано в Java 8 и 9, но не будет работать в Java 7 из-за отсутствия поддержки лямбда-выражений. Чтение файла размером 1 ГБ заняло около 3,5 секунд, что ставит его на второе место после чтения больших файлов.

Проверено на работу в Java 7, 8, 9. Для считывания тестового файла объемом 1 ГБ потребовалось около 4,5 секунд.

Читайте также: