В информатике файл - это ресурс, используемый для дискретной записи
данных в запоминающее устройство компьютера. В Java ресурс обычно
представляет собой объект, реализующий интерфейс AutoCloseable
Чтение файлов и ресурсов имеет множество применений:
- Статистика, аналитика и отчеты
- Машинное обучение
- Работа с большими текстовыми файлами или журналами
Иногда эти файлы могут быть абсурдно большими, в них хранятся гигабайты или терабайты, и их полное чтение неэффективно.
Возможность читать файл построчно дает нам возможность искать только релевантную информацию и останавливать поиск, как только мы нашли то, что ищем. Это также позволяет нам разбивать данные на логические части, как если бы файл был отформатирован в формате 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
также имеет lines
который возвращает Stream
. Этот
поток содержит строки, которые были прочитаны BufferedReader
как его
элементы.
Вы можете легко преобразовать этот поток в список, если вам нужно:
List<String> list = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
list = br.lines().collect(Collectors.toList());
}
Чтение этого списка аналогично чтению потока, которое рассматривается в следующем разделе:
list.forEach(System.out::println);
Потоки Java 8
Если вы уже знакомы с потоками Java 8, вы можете использовать их как более чистую альтернативу устаревшему циклу:
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
stream.forEach(System.out::println);
}
Здесь мы снова используем синтаксис try-with-resources , инициализируя
поток строк с помощью статического вспомогательного метода
Files.lines()
System.out::println
используется в демонстрационных
целях, и вы должны заменить ее любым кодом, который вы будете
использовать для обработки своих строк текста.
Помимо чистого API, потоки очень полезны, когда вы хотите применить несколько операций к данным или отфильтровать что-то.
Предположим, у нас есть задача напечатать все строки, которые находятся в данном текстовом файле, и оканчиваются символом «/». Строки следует преобразовать в верхний регистр и отсортировать по алфавиту.
Изменив наш первоначальный пример Streams API, мы получим очень чистую реализацию:
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
stream
.filter(s -> s.endswith("/"))
.sorted()
.map(String::toUpperCase)
.forEach(System.out::println);
}
Метод filter()
возвращает поток, состоящий из элементов этого потока,
соответствующих заданному предикату. В нашем случае мы оставляем только
те, которые заканчиваются на «/».
Метод map()
возвращает поток, состоящий из результатов применения
данной функции к элементам этого потока.
Метод toUpperCase()
String
помогает нам достичь желаемого результата
и используется здесь как ссылка на метод, как и println
из нашего
предыдущего примера.
Метод sorted()
возвращает поток, состоящий из элементов этого потока,
отсортированных в соответствии с естественным порядком. Вы также можете
предоставить собственный Comparator
, и в этом случае сортировка будет
выполняться в соответствии с ним.
Хотя порядок операций может быть изменен для методов filter()
,
sorted()
и map()
forEach()
всегда следует помещать в конец,
поскольку это операция терминала. Он возвращает void
и в этом
отношении к нему больше ничего нельзя привязать.
Apache Commons
Если вы уже используете Apache Commons в
своем проекте, вы можете использовать помощник, который считывает все
строки из файла в List<String>
:
List<String> lines = FileUtils.readLines(file, "UTF-8");
for (String line: lines) {
// process the line
}
Помните, что этот подход считывает все строки из файла в lines
и
только после этого начинается выполнение цикла for
. Это может занять
много времени, и вам следует дважды подумать, прежде чем использовать
его для больших текстовых файлов.
Заключение
В Java существует несколько способов чтения файла построчно, и выбор подходящего подхода полностью зависит от программиста. Вы должны подумать о размере файлов, которые вы планируете обрабатывать, требованиях к производительности, стиле кода и библиотеках, которые уже есть в проекте. Обязательно протестируйте некоторые угловые случаи, такие как огромные, пустые или несуществующие файлы, и вам будет хорошо использовать любой из предоставленных примеров.