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)
adeshere
07.06.2023 09:37+1Прошу прощения, если вопрос дурацкий - но ведь статья вроде бы рассчитана на новичков? Вот я как раз такой новичок и есть: язык не использую, но крокодил мимо, и заглянул, чтобы узнать ответ на вопрос: в каких случаях
FileReader
предпочтительнее, чемBufferedReader
? Было интересно узнать, как это устроеноу соседей
у меня в языке тоже можно эмулировать аналогичные конструкции, и там я знаю одно преимущество: если мне лень заранее проверить длину файла (или это не файл), то
FileReader
сообщит про конец файла с точностью до символа, а при использованииBufferedReader
мне это придется выяснять самомуУвы, но из статьи я смог лишь понять, что
BufferedReader
всегда лучше. А зачем тогдаFileReader
?qvan
07.06.2023 09:37+1BufferedReader читает из потока, у него нет метода чтения из файла, для того, чтобы читать из файла ему нужен FilerReader или FileInputStream. Если проваливаться дальше, то в данных примерах нет особого выигрыша в перфомансе, так как все упирается в один метод чтения.
Захват файла. Читаем один байт. Отпускаем файл. Добавляем в оперативку.
-
Захват файла. Читаем н байт до конца строки по одному из файла. Отпускаем файл. Добавляем н байт в оперативку. Работаем с массивом байтом из оперативки: добавляем в другой массив.
метод чтения в первом и втором случае одинаковый. И он native, то есть написан не на Java.
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 вовсе не "лучше". Он просто применяется к другому ридеру, и буферизует поток данных, чтобы дело шло быстрее. То есть это буфер. Поверх другого ридера. В общем случае любого. Который сам по себе бесполезен.
adeshere
07.06.2023 09:37@sshikovспасибо! Ваш комментарий для меня оказался полезней статьи. Впрочем, на Хабре это часто бывает ;-)
sshikov
07.06.2023 09:37+1Да не за что. Совет — научитесь и не ленитесь читать код классов, который либо доступен, либо легко декомпилируется скажем IDEA, либо Javadoc. Они конечно не идеальны, но разницу между классами таки можно понять. Во-первых, они зачастую понятнее документации, а во-вторых, они как правило не врут (код уж точно).
IceWanderer
07.06.2023 09:37+1@adeshere еще лучше не ленитесь читать книги по сертификации Java, там все это подробно расписано, ответы на свои вопросы вы там точно найдете. Конкретно эта тема освещается в OCP-ll.
adeshere
07.06.2023 09:37@IceWanderer, Ваш совет мудрый, но к сожалению, у меня немного другая специфика. Дело в том, что я не планирую изучать Java или писать на нем, и заглянул сюда только за идеями, а не за конкретикой
А для знакомства именно с идеями книги по сертификации - это не самый идеальный инструмент...
Вообще, у меня совсем другая специальность, а программированием я занимаюсь попутно для работы с геофизическими рядами данных. Тем не менее, когда-то в очень далеком DOS-овском прошлом я написал свои аналоги обсуждаемых функций, так как встроенная в мой язык буферизация тогда работала с очень большими ограничениями. И с тех пор это legacy является основой большинства моих функций доступа к файлам с данными и конфигам (разумеется, развиваясь и допиливаясь под новые задачи). Поэтому я заглянул в эту тему, чтобы присмотреть что-то полезное для себя с точки зрения идей: какие еще фичи потенциально могут быть полезными в моей ситуации, не нужно ли мне что-то еще в свой фреймворк прикрутить? Обычно статьи на Хабре как раз и позволяют за пару минут ухватить суть вопроса. И уже после этого, в зависимости от результатов, либо сразу бежать кодировать, либо разочарованно утопиться. Ну или сесть и заняться раскуриванием доков. А просто так, без наводки, перечитать все доки по всем языкам - никакой жизни не хватит. Ведь для меня читать доки и вообще самообразовываться - это
скорее удовольствие, чем работа
Напрямую (в ближайшей перспективе) это никак на мою результативность не влияет. И в будущем если и повлияет, то только косвенно (я уже давно на одно месте работаю, и вроде как себя там нашел, поэтому крайне маловероятно, что в программеры когда-то уйду).
Поэтому я на Хабр заглядываю больше для отдыха и расширения кругозора, чем по необходимости. И вот с этой точки зрения чтение хороших статей (и комментов к ним) гораздо полезнее (да и приятнее), чем чтение доков
sshikov
07.06.2023 09:37Никогда не понимал, зачем нужны такие сертификаты. На практике нормально использовать скажем процентов 20 возможностей языка.
Я не хочу сказать, что сертификаты не нужны никому. Возможно, есть такая работа, где подобный сертификат реально пригодится, но как по мне — о ваших умениях программировать он говорит довольно мало. И на интервью, скажем у меня, скорее вызывает мысли типа: "А, тут кто-то претендует на звание гуру, дай-ка мы зададим ему вопросы посложнее". А оно вам надо, посложнее? :)
Впрочем, у вас написано не "получите сертификат", а "прочитайте книги", и в такой постановке я согласен. В конце концов, прочитать спецификацию языка — тоже хоть и очень муторно, потому что она большая и местами сложная, но в целом полезно.
IceWanderer
07.06.2023 09:37+1Совершенно верно, читать нужно для ознакомления, тоже не совсем понимаю ценность сертификатов. Но зато сразу становится понято что человек не читал базу по джаве когда на 8+ джаве пишет Date, а не LocalDate, например.
sshikov
07.06.2023 09:37Date, а не LocalDate, например
может быть где-то легаси API какое-то в проекте. В чужой зависимости. Бывают такие случаи, нередко.
Paranoich
07.06.2023 09:37Уже месяца 2 радовался, что подобные КДПВ исчезли с хабра.
Их какая то программа генерит? Я без шуток и наездов — правда не понимаю откуда они берутся.
Есть ещё одна категория — какие то толстые люди, плавающие в воздухе.
Это хуже, чем у вас.
Griphon
07.06.2023 09:37Несколько натянутый пример про побайтовое чтение. Редко когда читают посимвольно. Скорее массив символов.
Кроме того, как же кэш диска? Данные ситаются не байтами а секторами.
Кэш дисковых операций в операционной системе?
В примере с текстом в килобайты что один ридер что другой будут читать из памяти.
OlegKopeykin
07.06.2023 09:37-1Так же вопрос со стороны новичка, почему используется конструкция
while((line = br.readLine()) != null)
, а неwhile(br.ready())
Просто что бы сократить код и не писать в циклеbr.readLine()
?
atpshnik
Чем-то похоже на статью "SQL vs. CSS What’s the Difference? Which Is Better?". Почему именно и только эти два ридера выбраны для сравнения?