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

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

1. обзор

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

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

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

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

Проще говоря,it enables us to minimize the number of I/O operations by reading chunks of characters and storing them in an internal buffer. Пока в буфере есть данные, средство чтения будет читать из него, а не напрямую из основного потока.

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

Как и большинство классов ввода-вывода Java,BufferedReader implementsDecorator pattern, meaning it expects a Reader in its constructor.. Таким образом, он позволяет нам гибко расширять экземпляр реализацииReader с помощью функции буферизации:

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

Но если буферизация для нас не имеет значения, мы могли бы просто использоватьFileReader direct:

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

В дополнение к буферизацииBufferedReader also provides some nice helper functions for reading files line-by-line. Таким образом, даже если может показаться проще использоватьFileReader directly,BufferedReader может оказаться большим подспорьем.

2.2. Буферизация потока

В общем,we can configure BufferedReader to take any kind of input stream as an underlying source. Мы можем сделать это, используяInputStreamReader и обернув его в конструктор:

BufferedReader reader =
  new BufferedReader(new InputStreamReader(System.in));

В приведенном выше примере мы читаем изSystem.in w, что обычно соответствует вводу с клавиатуры. Точно так же мы могли бы передать поток ввода для чтения из сокета, файла или любого мыслимого типа текстового ввода. Единственным условием является наличие подходящей реализацииInputStream для этого.

2.3. BufferedReader против сканера

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

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

  • BufferedReader синхронизирован (потокобезопасен), а Scanner - нет.

  • Scanner сканировать синтаксический анализ примитивных типов и строк с использованием регулярных выражений

  • BufferedReader позволяет изменять размер буфера, в то время как Scanner имеет фиксированный размер буфера

  • BufferedReader имеет больший размер буфера по умолчанию

  • Scanner скрываетIOException, аBufferedReader заставляет нас справиться с этим

  • BufferedReader обычно быстрее, чемScanner, потому что он только читает данные, не разбирая их

Имея это в виду,if we are parsing individual tokens in a file, then Scanner will feel a bit more natural than BufferedReader. But, just reading a line at a time is where BufferedReader shines.

При необходимости у нас также естьa guide on Scanner.

3. Чтение текста сBufferedReader

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

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

Во-первых,let’s create a BufferedReader using its BufferedReader(Reader) constructor:

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 КБ).

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

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

Наконец,there is one more handy way to create a BufferedReader using the Files helper class изjava.nioAPI:

BufferedReader reader =
  Files.newBufferedReader(Paths.get("src/main/resources/input.txt"))

Создание его like - это хороший способ буферизации, если мы хотим прочитать файл, потому что нам не нужно вручную создаватьFileReader first, а затем оборачивать его.

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

We can do the same thing as above using the lines method introduced in 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();
}

Это прочитает символы (возвращенные как значения 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 иreset

Мы можем использовать методы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 означает, что только код запомнит метку на один символ вперед. 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.

Обратите внимание: посколькуmark() может выдаватьUnsupportedOperationException, довольно часто связываютmarkSupported() с кодом, который вызываетmark(). , хотя на самом деле он нам здесь не нужен. That’s because markSupported() always returns true for BufferedReader.с

Конечно, мы могли бы сделать вышеупомянутое чуть более элегантно другими способами, и действительно,mark andreset не очень типичные методы. They certainly come in handy, though, when there is a need to look ahead.

5. Заключение

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

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