Руководство по BufferedReader

1. Обзор

BufferedReader - это класс, который упрощает чтение текста из потока ввода символов. Он буферизует символы, чтобы обеспечить эффективное чтение текстовых данных.

В этом руководстве мы рассмотрим, как использовать класс BufferedReader .

2. Когда использовать BufferedReader

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

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

2.1. Буферизация другого читателя

Как и большинство классов ввода-вывода Java, BufferedReader реализует шаблон Decorator, то есть ожидает 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 против сканера

В качестве альтернативы мы могли бы использовать класс Scanner для достижения той же функциональности, что и BufferedReader.

Однако между этими двумя классами есть существенные различия, которые могут сделать их более или менее удобными для нас, в зависимости от нашего варианта использования:

  • BufferedReader синхронизирован (потокобезопасен), а Scanner - нет.
  • Сканер может анализировать примитивные типы и строки с помощью регулярных выражений.
  • BufferedReader позволяет изменять размер буфера, в то время как Scanner имеет фиксированный размер буфера
  • BufferedReader имеет больший размер буфера по умолчанию
  • Сканер скрывает IOException , а BufferedReader заставляет нас его обрабатывать
  • BufferedReader обычно быстрее, чем Scanner, потому что он только читает данные, не разбирая их

Имея это в виду, если мы анализируем отдельные токены в файле, Scanner будет казаться немного более естественным, чем BufferedReader. Но простое чтение строки за раз - это то, чем выделяется BufferedReader .

При необходимости у нас также есть руководство по сканеру .

3. Чтение текста с помощью BufferedReader

Давайте рассмотрим весь процесс создания, использования и уничтожения BufferReader для чтения из текстового файла.

3.1. Инициализация BufferedReader

Во - первых, давайте создадим BufferedReader , используя его BufferedReader (Читатель) конструктор :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

Обертывание FileReader таким образом - хороший способ добавить буферизацию в качестве аспекта для других читателей.

По умолчанию это будет использовать буфер размером 8 КБ. Однако, если мы хотим буферизовать меньшие или большие блоки, мы можем использовать конструктор BufferedReader (Reader, int) :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt")), 16384);

Это установит размер буфера в 16384 байта (16 КБ).

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

Лучше всего использовать степень 2 в качестве размера буфера, поскольку большинство аппаратных устройств имеют степень 2 в качестве размера блока.

Наконец, есть еще один удобный способ создать BufferedReader с помощью вспомогательного класса Files из API java.nio :

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(); }

Мы можем сделать то же самое, что и выше, используя метод строк, представленный в Java 8, немного проще:

public String readAllLinesWithStream(BufferedReader reader) { return reader.lines() .collect(Collectors.joining(System.lineSeparator())); }

3.3. Закрытие потока

После использования BufferedReader мы должны вызвать его метод close (), чтобы освободить все связанные с ним системные ресурсы. Это делается автоматически, если мы используем блок try-with-resources :

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(); }

This will read the characters (returned as ASCII values), cast them to char and append them to the result. We repeat this until the end of the stream, which is indicated by the response value -1 from the read() method.

4.2. Reading Multiple Characters

If we want to read multiple characters at once, we can use the method 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; }

In the above code example, we'll read up to 5 characters into a char array and construct a string from it. In the case that no characters were read in our read attempt (i.e. we've reached the end of the stream), we'll simply return an empty string.

4.3. Skipping Characters

We can also skip a given number of characters by calling the skip(long n) method:

@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); }

In the above example, we read from an input string which contains numbers separated by two underscores. In order to construct a string containing only the numbers, we are skipping the underscores by calling the skip method.

4.4. mark and reset

We can use the mark(int readAheadLimit) and reset() methods to mark some position in the stream and return to it later. As a somewhat contrived example, let's use mark() and reset() to ignore all whitespaces at the beginning of a stream:

@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); }

In the above example, we use the mark() method to mark the position we just read. Giving it a value of 1 means only the code will remember the mark for one character forward. It's handy here because, once we see our first non-whitespace character, we can go back and re-read that character without needing to reprocess the whole stream. Without having a mark, we'd lose the L in our final string.

Note that because mark() can throw an UnsupportedOperationException, it's pretty common to associate markSupported() with code that invokes mark(). Though, we don't actually need it here. That's because markSupported() always returns true for BufferedReader.

Of course, we might be able to do the above a bit more elegantly in other ways, and indeed mark and reset aren't very typical methods. They certainly come in handy, though, when there is a need to look ahead.

5. Conclusion

В этом кратком руководстве мы узнали, как читать потоки ввода символов на практическом примере с использованием BufferedReader .

Наконец, исходный код примеров доступен на Github.