1. Обзор
BufferedReader – это класс, который упрощает чтение текста из потока ввода символов. Он буферизует символы, чтобы обеспечить эффективное чтение текстовых данных.
В этом уроке мы рассмотрим, как использовать класс BufferedReader /.
2. Когда использовать BufferedReader
В общем, BufferedReader пригодится, если мы хотим читать текст из любого источника ввода, будь то файлы, сокеты или что-то еще.
Проще говоря, это позволяет нам минимизировать количество операций ввода-вывода, считывая фрагменты символов и сохраняя их во внутреннем буфере. Пока в буфере есть данные, считыватель будет считывать их из него, а не непосредственно из базового потока.
2.1. Буферизация Другого Считывателя
Как и большинство классов ввода-вывода Java,/ | BufferedReader реализует Шаблон декоратора, что означает, что он ожидает Reader в своем конструкторе. Таким образом, это позволяет нам гибко расширять экземпляр реализации Reader с функцией буферизации:
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));
Но, если буферизация не имеет для нас значения, мы могли бы просто использовать FileReader напрямую:
FileReader reader = new FileReader("src/main/resources/input.txt");
В дополнение к буферизации, BufferedReader также предоставляет некоторые полезные вспомогательные функции для чтения файлов построчно . Таким образом, хотя может показаться, что проще использовать FileReader напрямую, BufferedReader может быть большой помощью.
2.2. Буферизация потока
В общем, мы можем настроить BufferedReader для использования любого типа входного потока | в качестве базового источника . Мы можем сделать это с помощью InputStreamReader и обернуть его в конструктор:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
В приведенном выше примере мы читаем из System.in , который обычно соответствует вводу с клавиатуры. Аналогично, мы могли бы передать входной поток для чтения из сокета, файла или любого мыслимого типа текстового ввода. Единственным условием является наличие подходящего InputStream реализация для него.
2.3. BufferedReader vs Сканер
В качестве альтернативы мы могли бы использовать класс Scanner для достижения той же функциональности, что и с BufferedReader.
Однако между этими двумя классами существуют существенные различия, которые могут сделать их более или менее удобными для нас, в зависимости от нашего варианта использования:
- BufferedReader синхронизируется (потокобезопасно), а сканер – нет
- Сканер может анализировать примитивные типы и строки с помощью регулярных выражений
- BufferedReader позволяет изменять размер буфера, в то время как сканер не имеет фиксированного размера буфера
- BufferedReader имеет больший размер буфера по умолчанию
- Сканер скрывает IOException , в то время как BufferedReader заставляет нас обрабатывать его
- BufferedReader обычно быстрее, чем Сканер , потому что он только считывает данные без их анализа
Имея это в виду, если мы анализируем отдельные токены в файле, то Scanner будет чувствовать себя немного более естественно, чем BufferedReader. Но просто чтение строки за раз-это то, где BufferedReader сияет.
При необходимости у нас также есть руководство по сканеру.
3. Чтение Текста С Помощью BufferedReader
Давайте пройдем весь процесс создания, использования и уничтожения BufferReader для правильного чтения из текстового файла.
3.1. Инициализация BufferedReader
Во-первых, давайте создадим BufferedReader , используя его BufferedReader(Reader) конструктор :
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));
Обертывание File Reader подобным образом-хороший способ добавить буферизацию в качестве аспекта для других читателей.
По умолчанию для этого будет использоваться буфер размером 8 КБ. Однако, если мы хотим буферизировать меньшие или большие блоки, мы можем использовать конструктор BufferedReader(Reader, int) :
BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt")), 16384);
Это установит размер буфера равным 16384 байтам (16 КБ).
Оптимальный размер буфера зависит от таких факторов, как тип входного потока и аппаратное обеспечение, на котором выполняется код. По этой причине, чтобы достичь идеального размера буфера, мы должны найти его сами, экспериментируя.
Лучше всего использовать мощность 2 в качестве размера буфера, так как большинство аппаратных устройств имеют мощность 2 в качестве размера блока.
Наконец, есть еще один удобный способ создать BufferedReader с помощью Файлов вспомогательного класса из java.nio |/API:
BufferedReader reader = Files.newBufferedReader(Paths.get("src/main/resources/input.txt"))
Создание его подобным образом-хороший способ буферизации, если мы хотим прочитать файл, потому что нам не нужно сначала вручную создавать FileReader , а затем обертывать его.
3.2. Чтение построчно
Затем давайте прочитаем содержимое файла с помощью метода readLine :
public String readAllLines(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); content.append(System.lineSeparator()); } return content.toString(); }
Мы можем сделать то же самое, что и выше, используя метод lines , введенный в Java 8 немного проще:
public String readAllLinesWithStream(BufferedReader reader) { return reader.lines() .collect(Collectors.joining(System.lineSeparator())); }
3.3. Закрытие потока
После использования BufferedReader мы должны вызвать его метод close () , чтобы освободить все системные ресурсы, связанные с ним. Это делается автоматически, если мы используем try-with-resources block:
try (BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"))) { return readAllLines(reader); }
4. Другие Полезные Методы
Теперь давайте сосредоточимся на различных полезных методах, доступных в BufferedReader.
4.1. Чтение одного символа
Мы можем использовать метод read() для чтения одного символа. Давайте прочитаем весь контент по символам до конца потока:
public String readAllCharsOneByOne(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); int value; while ((value = reader.read()) != -1) { content.append((char) value); } return content.toString(); }
Это приведет к считыванию символов (возвращаемых в виде значений ASCII), приведению их к char и добавлению их к результату. Мы повторяем это до конца потока, на что указывает значение ответа -1 из метода read () .
4.2. Чтение Нескольких Символов
Если мы хотим прочитать несколько символов одновременно, мы можем использовать метод read(char[] cbuf, int off, int len) :
public String readMultipleChars(BufferedReader reader) throws IOException { int length; char[] chars = new char[length]; int charsRead = reader.read(chars, 0, length); String result; if (charsRead != -1) { result = new String(chars, 0, charsRead); } else { result = ""; } return result; }
В приведенном выше примере кода мы прочитаем до 5 символов в массив символов и построим из него строку. В случае, если в нашей попытке чтения не было прочитано ни одного символа (т. Е. Мы достигли конца потока), мы просто вернем пустую строку.
4.3. Пропуск Символов
Мы также можем пропустить заданное количество символов, вызвав метод skip(long n) :
@Test public void givenBufferedReader_whensSkipChars_thenOk() throws IOException { StringBuilder result = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new StringReader("1__2__3__4__5"))) { int value; while ((value = reader.read()) != -1) { result.append((char) value); reader.skip(2L); } } assertEquals("12345", result); }
В приведенном выше примере мы читаем из входной строки, которая содержит числа, разделенные двумя символами подчеркивания. Чтобы построить строку, содержащую только числа, мы пропускаем подчеркивания, вызывая метод skip .
4.4. пометка и сброс
Мы можем использовать методы mark(int readAheadLimit) и reset () , чтобы отметить некоторую позицию в потоке и вернуться к ней позже. В качестве несколько надуманного примера давайте использовать mark() и reset () , чтобы игнорировать все пробелы в начале потока:
@Test public void givenBufferedReader_whenSkipsWhitespacesAtBeginning_thenOk() throws IOException { String result; try (BufferedReader reader = new BufferedReader(new StringReader(" Lorem ipsum dolor sit amet."))) { do { reader.mark(1); } while(Character.isWhitespace(reader.read())) reader.reset(); result = reader.readLine(); } assertEquals("Lorem ipsum dolor sit amet.", result); }
В приведенном выше примере мы используем метод mark () , чтобы отметить позицию, которую мы только что прочитали. Присвоение ему значения 1 означает, что только код запомнит метку на один символ вперед. Это удобно здесь, потому что, как только мы увидим наш первый символ без пробелов, мы сможем вернуться и перечитывать этот символ без необходимости повторной обработки всего потока. Не имея метки, мы потеряем L в нашей последней строке.
Обратите внимание , что, поскольку mark() может вызвать исключение UnsupportedOperationException , довольно часто связывают markSupported() с кодом, который вызывает mark (). Хотя на самом деле он нам здесь не нужен. Это потому, что markSupported() всегда возвращает true для BufferedReader .
Конечно, мы могли бы сделать это немного более элегантно другими способами, и действительно, mark и reset не очень типичные методы. Они, безусловно, пригодятся, хотя, когда есть необходимость смотреть вперед .
5. Заключение
В этом кратком руководстве мы узнали, как читать потоки ввода символов на практическом примере с помощью BufferedReader .
Наконец, исходный код для примеров доступен на Github .