BufferedReader Java: эффективное чтение файлов в Java
Эффективная работа с текстовыми файлами - одна из важнейших задач при разработке приложений на Java. В этой статье мы подробно рассмотрим как ускорить чтение файлов в Java с помощью класса BufferedReader.
Обзор BufferedReader в Java
BufferedReader - это класс в стандартной библиотеке Java, предназначенный для эффективного посимвольного чтения потока символов. Он реализует буферизацию ввода, что позволяет существенно ускорить чтение из медленных источников данных вроде файлов, сетевых сокетов и т.д.
Основные преимущества использования BufferedReader:
- Высокая скорость чтения за счет использования внутреннего буфера
- Удобные методы для построчного чтения текстовых файлов
- Возможность чтения из любого источника, реализующего интерфейс Reader
По сравнению с обычным FileReader, BufferedReader позволяет увеличить скорость чтения файлов в несколько раз за счет сокращения числа обращений к диску.
Однако при чтении небольших файлов выигрыша от использования буферизации может не наблюдаться. Поэтому для чтения логов, конфигурационных файлов и других небольших текстовых данных можно обойтись и обычным FileReader.
Создание экземпляра BufferedReader
Для работы с BufferedReader нужно создать его экземпляр, передав в конструктор другой объект Reader для чтения базового потока:
FileReader fileReader = new FileReader("data.txt");
BufferedReader reader = new BufferedReader(fileReader);
Здесь в качестве источника данных для буферизации используется FileReader. Но на самом деле можно использовать любой другой Reader, например InputStreamReader для чтения из сокета или ByteArrayInputStream.
Помимо основного конструктора, в BufferedReader есть еще один, который позволяет явно указать размер буфера в байтах:
BufferedReader reader = new BufferedReader(fileReader, 1024);
По умолчанию используется буфер в 8 Кбайт, но для некоторых задач может потребоваться его настройка. Например, при чтении больших файлов имеет смысл увеличить размер буфера до 16-64 Кбайт.
Чтение данных с помощью BufferedReader
Для чтения данных из потока в BufferedReader предоставляется 3 основных метода:
- read() - читает один символ
- read(char[] buffer) - читает блок символов в указанный массив
- readLine() - читает одну строку текста
Рассмотрим их использование на примерах.
Чтение файла посимвольно с помощью read():
int c; while((c = reader.read()) != -1) { System.out.print((char)c); }
Здесь мы читаем символы в цикле, пока не будет достигнут конец потока (он помечается возвратом -1).
А вот пример чтения в массив:
char[] buf = new char[1024]; int charsRead = reader.read(buf);
В переменной charsRead будет число реально прочитанных символов.
Наконец, читаем файл по строкам:
String line; while((line = reader.readLine()) != null) { System.out.println(line); }
Метод readLine() автоматически читает все символы до конца строки или конца файла.
Закрытие потока BufferedReader
После того, как мы закончили чтение данных с помощью BufferedReader, важно не забыть закрыть используемый поток с помощью метода close():
reader.close();
Это позволит освободить системные ресурсы, выделенные под поток. Кроме того, для автоматического закрытия потока удобно использовать конструкцию try-with-resources:
try(BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) { // ... работа с читателем }
В этом случае поток будет закрыт автоматически по завершении блока try.
Дополнительные возможности BufferedReader
Кроме основных методов чтения, BufferedReader предоставляет и некоторые дополнительные возможности. Рассмотрим некоторые из них.
Чтение из разных источников
Одно из главных преимуществ BufferedReader в том, что он позволяет читать данные из любого источника, реализующего интерфейс Reader. Это может быть файл, сетевой поток, консоль или что угодно еще.
Например, реализуем буферизацию при чтении из сокета:
Socket socket = new Socket("example.com", 80); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
А вот пример чтения из консоли:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line = reader.readLine(); // ждем ввода строки
Сравнение производительности
Чтобы наглядно увидеть выигрыш в производительности от использования BufferedReader, можно реализовать чтение одного и того же файла с ним и без него и сравнить скорость.
Например:
// Чтение с помощью FileReader long start = System.currentTimeMillis(); // читаем файл long end = System.currentTimeMillis(); System.out.println("Время чтения с FileReader: " + (end - start) + "мс"); // Чтение с помощью BufferedReader start = System.currentTimeMillis(); // читаем файл end = System.currentTimeMillis(); System.out.println("Время чтения с BufferedReader: " + (end - start) + "мс");
Для больших файлов разница может быть в несколько раз!
BufferedReader против Scanner
Еще одним распространенным способом чтения текстовых данных в Java является класс Scanner. В чем же разница между ним и BufferedReader?
Основные отличия:
- Scanner умеет парсить примитивные типы, а BufferedReader только строки
- У Scanner фиксированный размер буфера, а в BufferedReader можно настроить
- BufferedReader немного быстрее за счет того, что не парсит данные
- BufferedReader потокобезопасный, Scanner - нет
В целом, для простого построчного чтения текста BufferedReader подходит лучше. А Scanner удобен, когда нужен парсинг входных данных.
Лучшие практики использования BufferedReader
В заключение дадим несколько полезных советов по использованию BufferedReader.
Выбор размера буфера
Как уже упоминалось, по умолчанию используется буфер 8Кб. Но для оптимальной производительности он может потребовать настройки. Например:
- Для чтения с сети/консоли лучше буфер поменьше, 256-2048 байт
- Для чтения больших файлов с диска буфер побольше, 16-64Кб
Многопоточное чтение
Поскольку BufferedReader потокобезопасный, его можно смело использовать для чтения из одного потока в нескольких потоках одновременно без дополнительной синхронизации.
Обработка исключений
Методы BufferedReader могут выбрасывать исключения IOException, поэтому их нужно оборачивать в try/catch.
Либо проще использовать уже упомянутый try-with-resources, чтобы не писать try/catch вручную.
Интеграция со Stream API
Можно использовать BufferedReader вместе со Stream API, с помощью метода lines():
BufferedReader reader = Files.newBufferedReader(path); long count = reader.lines().count();
Это позволит применять все преимущества Stream API при чтении текстовых файлов.