Автор оригинала: Vasyl Lagutin.
В информатике файл – это ресурс, используемый для дискретной записи данных в запоминающее устройство компьютера. В Java ресурс обычно представляет собой объект, реализующий автоклавируемый
интерфейс.
Чтение файлов и ресурсов имеет множество применений:
- Статистика, аналитика и отчеты
- Машинное Обучение
- Работа с большими текстовыми файлами или журналами
Иногда эти файлы могут быть абсурдно большими, с хранящимися гигабайтами или терабайтами, и полное чтение их неэффективно.
Возможность читать файл строка за строкой дает нам возможность искать только соответствующую информацию и прекращать поиск, как только мы найдем то, что ищем. Это также позволяет нам разбивать данные на логические части, например, если файл был отформатирован в формате CSV.
Есть несколько различных вариантов на выбор, когда вам нужно прочитать файл строка за строкой.
Сканер
Один из самых простых способов чтения файла строка за строкой в Java может быть реализован с помощью класса Scanner . Сканер разбивает свой ввод на маркеры, используя шаблон разделителя, которым в нашем случае является символ новой строки:
Scanner scanner = new Scanner(new File("filename")); while (scanner.hasNextLine()) { String line = scanner.nextLine(); // process the line }
Метод hasNextLine()
возвращает true
, если на входе этого сканера есть другая строка, но сам сканер в этот момент не проходит мимо каких-либо входных данных или не считывает какие-либо данные.
Чтобы прочитать строку и двигаться дальше, мы должны использовать метод nextLine ()
. Этот метод продвигает сканер дальше текущей строки и возвращает ввод, который не был достигнут изначально. Этот метод возвращает остальную часть текущей строки, исключая любой разделитель строк в конце строки. Затем позиция чтения устанавливается в начало следующей строки, которая будет прочитана и возвращена при повторном вызове метода.
Поскольку этот метод продолжает поиск по входным данным в поисках разделителя строк, он может буферизировать все входные данные при поиске конца строки, если разделители строк отсутствуют.
Буферизованное считывающее устройство
Класс BufferedReader представляет собой эффективный способ считывания символов, массивов и строк из потока ввода символов.
Как описано в именовании, этот класс использует буфер. Объем буферизованных данных по умолчанию составляет 8192 байта, но по соображениям производительности он может быть установлен на пользовательский размер:
BufferedReader br = new BufferedReader(new FileReader(file), bufferSize);
Файл или, скорее, экземпляр класса File
не является подходящим источником данных для BufferedReader
, поэтому нам нужно использовать FileReader
, который расширяет InputStreamReader
. Это удобный класс для чтения информации из текстовых файлов, и он не обязательно подходит для чтения необработанного потока байтов:
try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; while ((line = br.readLine()) != null) { // process the line } }
Инициализация буферизованного считывателя была написана с использованием синтаксиса try-with-resources , специфичного для Java 7 или выше. Если вы используете более старую версию, вам следует инициализировать переменную br
перед оператором try
и закрыть ее в блоке finally
.
Вот пример предыдущего кода без синтаксиса try-with-resources:
BufferedReader br = new BufferedReader(new FileReader(file)); try { String line; while ((line = br.readLine()) != null) { // process the line } } finally { br.close(); }
Код будет проходить по строкам предоставленного файла и остановится, когда встретится со строкой null
, которая является концом файла.
Не путайтесь, так как null
не равно пустой строке, и файл будет прочитан до конца.
Метод линий
Класс BufferedReader
также имеет метод строк
, который возвращает Поток
. Этот поток содержит строки , которые были прочитаны BufferedReader
в качестве его элементов.
Вы можете легко преобразовать этот поток в список, если вам нужно:
Listlist = new ArrayList<>(); try (BufferedReader br = new BufferedReader(new FileReader(file))) { list = br.lines().collect(Collectors.toList()); }
Чтение этого списка аналогично чтению в потоке, которые рассматриваются в следующем разделе:
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
list.forEach(System.out::println);
Потоки Java 8
Если вы уже знакомы с Java 8 Streams , вы можете использовать их в качестве более чистой альтернативы устаревшему циклу:
try (Streamstream = Files.lines(Paths.get(fileName))) { stream.forEach(System.out::println); }
Здесь мы снова используем синтаксис try-with-resources , инициализируя поток строк с помощью Files.lines()
статического вспомогательного метода. Ссылка на метод System.out::println
используется для демонстрационных целей, и вам следует заменить ее любым кодом, который вы будете использовать для обработки своих строк текста.
В дополнение к чистому API потоки очень полезны, когда вы хотите применить несколько операций к данным или отфильтровать что-то.
Давайте предположим, что у нас есть задача распечатать все строки, которые находятся в данном текстовом файле и заканчиваются символом”/”. Строки должны быть преобразованы в верхний регистр и отсортированы в алфавитном порядке.
Изменив наш первоначальный пример “API потоков”, мы получим очень чистую реализацию:
try (Streamstream = Files.lines(Paths.get(fileName))) { stream .filter(s -> s.endswith("/")) .sorted() .map(String::toUpperCase) .forEach(System.out::println); }
Метод filter()
возвращает поток, состоящий из элементов этого потока, соответствующих данному предикату. В нашем случае мы оставляем только те, которые заканчиваются на “/”.
Метод map()
возвращает поток, состоящий из результатов применения данной функции к элементам этого потока.
Метод toUpperCase()
класса String
помогает нам достичь желаемого результата и используется здесь в качестве ссылки на метод, как и вызов println
из нашего предыдущего примера.
Метод sorted()
возвращает поток, состоящий из элементов этого потока, отсортированных в соответствии с естественным порядком. Вы также можете предоставить пользовательский Компаратор
, и в этом случае сортировка будет выполняться в соответствии с ним.
Хотя порядок операций может быть изменен для методов filter()
, sorted ()
и map ()
, forEach()
всегда следует размещать в конце, поскольку это терминальная операция. Он возвращает пустоту
и, если на то пошло, ничто не может быть приковано к нему дальше.
Apache Commons
Если вы уже используете Apache Commons в своем проекте, вы можете использовать помощника, который считывает все строки из файла в Список<Строка>
:
Listlines = FileUtils.readLines(file, "UTF-8"); for (String line: lines) { // process the line }
Помните, что при таком подходе все строки из файла считываются в список строк
, и только после этого начинается выполнение цикла для
. Это может занять значительное количество времени, и вам следует дважды подумать, прежде чем использовать его в больших текстовых файлах.
Вывод
В Java существует несколько способов чтения файла строка за строкой, и выбор подходящего подхода полностью зависит от решения программиста. Вам следует подумать о размере файлов, которые вы планируете обрабатывать, требованиях к производительности, стиле кода и библиотеках, которые уже есть в проекте. Обязательно протестируйте некоторые угловые случаи, такие как огромные, пустые или несуществующие файлы, и вы сможете использовать любой из приведенных примеров.