1. Обзор

FileReader и BufferedReader — два класса, которые могут считывать символы из входного потока.

В этом туториале мы с вами рассмотрим различия между ними.

2. FileReader

Класс FileReader предназначен для чтения символьного потока из файла. К сожалению, он может читать файл только посимвольно, и каждый раз, когда мы вызываем его метод read(), он напрямую обращается к файлу на жестком диске, чтобы прочитать из него ровно один символ. В результате FileReader — очень медленный и неэффективный способ чтения символов из файлов. Кроме того, FileReader не может читать символы ни из каких типов потоков входных данных, кроме файлов.

2.1. Конструкторы

FileReader имеет три конструктора:

  • FileReader(File file): получает в качестве аргумента экземпляр файла

  • FileReader(FileDescriptor fd): получает в качестве аргумента дескриптор файла

  • FileReader(String fileName): получает в качестве аргумента имя файла (включая его путь)

2.2. Что он возвращает

Каждый раз, когда мы вызываем метод read(), он возвращает целое число, представляющее значение символа в формате Unicode, который был прочитан из файла, или -1, если достигнут конец символьного потока.

2.3. Пример

Давайте посмотрим на использование FileReader в коде примера, в котором мы будем считывать символы из текстового файла, содержащего всего одну строку “qwerty”:

@Test
public void whenReadingAFile_thenReadsCharByChar() {
    StringBuilder result = new StringBuilder();

    try (FileReader fr = new FileReader("src/test/resources/sampleText2.txt")) {
        int i = fr.read();

        while(i != -1) {
            result.append((char)i);

            i = fr.read();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    assertEquals("qwerty", result.toString());
}

В приведенном выше коде мы приводим возвращаемое значение из метода read() к типу char, а затем добавляем его в строку результата.

3. BufferedReader

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

Его конструктор требует в качестве входного символьного потока объект типа Reader. Следовательно, мы можем передать в качестве входного потока для чтения символов в BufferedReader любой класс, реализующий абстрактный класс Reader.

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

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

3.1. Конструкторы

BufferedReader имеет два конструктора:

  • BufferedReader(Reader in): в качестве аргумента получает поток символьных входных данных (который должен реализовывать абстрактный класс Reader

  • BufferedReader(Reader in, int sz): в качестве аргументов получает символьный поток и размер буфера 

3.2. Что он возвращает

Если мы вызовем метод read(), он возвращает целое число, представляющее значение символа в формате Unicode, который был прочитан из входного потока. Более того, если мы вызовем метод readLine(), он считает сразу целую строку из буфера и вернет ее как string.

3.3. Пример

Давайте посмотрим на пример использования BufferedReader для чтения символов из текстового файла, содержащего три строки:

@Test
public void whenReadingAFile_thenReadsLineByLine() {
    StringBuilder result = new StringBuilder();

    try (BufferedReader br = new BufferedReader(new FileReader("src/test/resources/sampleText1.txt"))) {
        String line;

        while((line = br.readLine()) != null) {
            result.append(line);
            result.append('\n');
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    assertEquals("first line\nsecond line\nthird line\n", result.toString());
}

Приведенный выше тестовый код выполняется без ошибок, а это означает, что BufferedReader успешно читает все три строки текста из файла.

4. В чем разница?

BufferedReader гораздо быстрее и эффективнее, чем FileReader поскольку он считывает целый блок данных из входного потока и сохраняет его в буфере для дальнейших вызовов метода чтения, в то время как FileReader должен получать доступ к файлу для считывания каждого символа. Более того, FileReader может читать файл только посимвольно, в то время как BufferedReader имеет такие полезные методы, как readLine(), который может считать из буфера сразу целую строку. И наконец, FileReader может производить чтение только из файла, а BufferedReader может читать из любого типа потока символьных входных данных (файл, консоль, строка и т.д.):

FileReader

BufferedReader

Медленнее и менее эффективен

Быстрее и эффективнее

Может читать только посимвольно

Может читать символы и строки

Можно читать только из файла

Может читать из любого символьного потока

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

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

В этом туториале мы с вами рассмотрели различия между FileReader и BufferedReader и когда их следует применять.

Как всегда, полный исходный код туториала доступен на GitHub.


Уже сегодня вечером состоится открытое занятие, на котором познакомимся с Reactor Kafka. На этом уроке посмотрим, как в java-приложении можно работать с Kafka в реактивном стиле. Разберемся, для чего это может быть полезно и когда стоит использовать.

Зарегистрироваться на открытое занятие можно на странице курса "Java Developer Professional".

Комментарии (16)


  1. atpshnik
    07.06.2023 09:37
    +7

    Чем-то похоже на статью "SQL vs. CSS What’s the Difference? Which Is Better?". Почему именно и только эти два ридера выбраны для сравнения?


  1. Vurtatoo
    07.06.2023 09:37
    +8

    Что лучше String или Spring, или Swing ?


  1. adeshere
    07.06.2023 09:37
    +1

    Прошу прощения, если вопрос дурацкий - но ведь статья вроде бы рассчитана на новичков? Вот я как раз такой новичок и есть: язык не использую, но крокодил мимо, и заглянул, чтобы узнать ответ на вопрос: в каких случаях FileReader предпочтительнее, чем BufferedReader? Было интересно узнать, как это устроено

    у соседей

    у меня в языке тоже можно эмулировать аналогичные конструкции, и там я знаю одно преимущество: если мне лень заранее проверить длину файла (или это не файл), то FileReader сообщит про конец файла с точностью до символа, а при использовании BufferedReader мне это придется выяснять самому

    Увы, но из статьи я смог лишь понять, что BufferedReader всегда лучше. А зачем тогда FileReader ?


    1. qvan
      07.06.2023 09:37
      +1

      BufferedReader читает из потока, у него нет метода чтения из файла, для того, чтобы читать из файла ему нужен FilerReader или FileInputStream. Если проваливаться дальше, то в данных примерах нет особого выигрыша в перфомансе, так как все упирается в один метод чтения.

      1. Захват файла. Читаем один байт. Отпускаем файл. Добавляем в оперативку.

      2. Захват файла. Читаем н байт до конца строки по одному из файла. Отпускаем файл. Добавляем н байт в оперативку. Работаем с массивом байтом из оперативки: добавляем в другой массив.

        метод чтения в первом и втором случае одинаковый. И он native, то есть написан не на Java.


    1. sshikov
      07.06.2023 09:37
      +3

      Потому что статья дурацкая. А ваш вопрос нормальный. Во-первых, интерфейс Reader (а остальные упомянутые классы его реализуют), это интерфейс для чтения из любых потоков символов. То есть, они работают с char[], и понимают, что такое кодировка, и сколько байтов нужно считать с диска, чтобы получить один символ UTF-16, к примеру.


      Direct Known Subclasses:
      BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader


      Заметьте, что тут нет FileReader (потому что он скорее всего не реализует Reader непосредственно, а является наследником InputStreamReader с FileInputStream в качестве входа).


      Вот кто судя по документации реализует Reader. Как можно догадаться, они умеют читать из CharArray, String, InputStream, а еще есть некие специальные Filter и Piped, которые делают сразу непонятно что. А еще есть PushBackReader, который может запихнуть символы обратно в поток. И понятно что много чего еще.


      Ну и BufferedReader, про который как раз речь.


      Ну так вот, BufferedReader вовсе не "лучше". Он просто применяется к другому ридеру, и буферизует поток данных, чтобы дело шло быстрее. То есть это буфер. Поверх другого ридера. В общем случае любого. Который сам по себе бесполезен.


      1. adeshere
        07.06.2023 09:37

        @sshikovспасибо! Ваш комментарий для меня оказался полезней статьи. Впрочем, на Хабре это часто бывает ;-)


        1. sshikov
          07.06.2023 09:37
          +1

          Да не за что. Совет — научитесь и не ленитесь читать код классов, который либо доступен, либо легко декомпилируется скажем IDEA, либо Javadoc. Они конечно не идеальны, но разницу между классами таки можно понять. Во-первых, они зачастую понятнее документации, а во-вторых, они как правило не врут (код уж точно).


          1. IceWanderer
            07.06.2023 09:37
            +1

            @adeshere еще лучше не ленитесь читать книги по сертификации Java, там все это подробно расписано, ответы на свои вопросы вы там точно найдете. Конкретно эта тема освещается в OCP-ll.


            1. adeshere
              07.06.2023 09:37

              @IceWanderer, Ваш совет мудрый, но к сожалению, у меня немного другая специфика. Дело в том, что я не планирую изучать Java или писать на нем, и заглянул сюда только за идеями, а не за конкретикой

              А для знакомства именно с идеями книги по сертификации - это не самый идеальный инструмент...

              Вообще, у меня совсем другая специальность, а программированием я занимаюсь попутно для работы с геофизическими рядами данных. Тем не менее, когда-то в очень далеком DOS-овском прошлом я написал свои аналоги обсуждаемых функций, так как встроенная в мой язык буферизация тогда работала с очень большими ограничениями. И с тех пор это legacy является основой большинства моих функций доступа к файлам с данными и конфигам (разумеется, развиваясь и допиливаясь под новые задачи). Поэтому я заглянул в эту тему, чтобы присмотреть что-то полезное для себя с точки зрения идей: какие еще фичи потенциально могут быть полезными в моей ситуации, не нужно ли мне что-то еще в свой фреймворк прикрутить? Обычно статьи на Хабре как раз и позволяют за пару минут ухватить суть вопроса. И уже после этого, в зависимости от результатов, либо сразу бежать кодировать, либо разочарованно утопиться. Ну или сесть и заняться раскуриванием доков. А просто так, без наводки, перечитать все доки по всем языкам - никакой жизни не хватит. Ведь для меня читать доки и вообще самообразовываться - это

              скорее удовольствие, чем работа

              Напрямую (в ближайшей перспективе) это никак на мою результативность не влияет. И в будущем если и повлияет, то только косвенно (я уже давно на одно месте работаю, и вроде как себя там нашел, поэтому крайне маловероятно, что в программеры когда-то уйду).

              Поэтому я на Хабр заглядываю больше для отдыха и расширения кругозора, чем по необходимости. И вот с этой точки зрения чтение хороших статей (и комментов к ним) гораздо полезнее (да и приятнее), чем чтение доков


            1. sshikov
              07.06.2023 09:37

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


              Я не хочу сказать, что сертификаты не нужны никому. Возможно, есть такая работа, где подобный сертификат реально пригодится, но как по мне — о ваших умениях программировать он говорит довольно мало. И на интервью, скажем у меня, скорее вызывает мысли типа: "А, тут кто-то претендует на звание гуру, дай-ка мы зададим ему вопросы посложнее". А оно вам надо, посложнее? :)


              Впрочем, у вас написано не "получите сертификат", а "прочитайте книги", и в такой постановке я согласен. В конце концов, прочитать спецификацию языка — тоже хоть и очень муторно, потому что она большая и местами сложная, но в целом полезно.


              1. IceWanderer
                07.06.2023 09:37
                +1

                Совершенно верно, читать нужно для ознакомления, тоже не совсем понимаю ценность сертификатов. Но зато сразу становится понято что человек не читал базу по джаве когда на 8+ джаве пишет Date, а не LocalDate, например.


                1. sshikov
                  07.06.2023 09:37

                  Date, а не LocalDate, например
                  может быть где-то легаси API какое-то в проекте. В чужой зависимости. Бывают такие случаи, нередко.


  1. Paranoich
    07.06.2023 09:37

    Уже месяца 2 радовался, что подобные КДПВ исчезли с хабра.

    Их какая то программа генерит? Я без шуток и наездов — правда не понимаю откуда они берутся.

    Есть ещё одна категория — какие то толстые люди, плавающие в воздухе.

    Это хуже, чем у вас.


    1. GolovinDS
      07.06.2023 09:37

      Генерит человек, но потихоньку уходим от них)


  1. Griphon
    07.06.2023 09:37

    Несколько натянутый пример про побайтовое чтение. Редко когда читают посимвольно. Скорее массив символов.

    Кроме того, как же кэш диска? Данные ситаются не байтами а секторами.

    Кэш дисковых операций в операционной системе?

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


  1. OlegKopeykin
    07.06.2023 09:37
    -1

    Так же вопрос со стороны новичка, почему используется конструкция
    while((line = br.readLine()) != null)
    , а не
    while(br.ready())
    Просто что бы сократить код и не писать в цикле br.readLine() ?