BufferedReader и BufferedWriter это классы-обертки из пакета java.io, которые добавляют буфер к символьным потокам чтения и записи. Вместо обращения к диску на каждый символ они копят данные в памяти и работают с диском большими порциями. Попутно BufferedReader дает метод readLine() для чтения строки целиком, а BufferedWriter метод newLine() для переноса строки.

Кратко

  • Оба класса это обертки: BufferedReader оборачивает Reader, BufferedWriter оборачивает Writer.
  • Смысл буфера в скорости: данные копятся в памяти и уходят на диск большими порциями, а не по одному символу.
  • Размер буфера задают вторым параметром конструктора, иначе берется размер по умолчанию.
  • readLine() возвращает очередную строку или null в конце потока, поэтому его ставят условием цикла while.
  • BufferedWriter не пишет на диск сразу: данные уходят при заполнении буфера, а также при вызове flush() или close().
  • Открывать оба класса удобнее в try-with-resources: поток закроется сам, даже если возникнет исключение.

BufferedReader

Java класс BufferedReader читает текст из потока ввода символов, буферизуя прочитанные символы, чтобы обеспечить эффективное считывание символов, массивов и строк. Можно указать в конструкторе вторым параметром размер буфера.
Схема иерархии символьных потоков java.io: BufferedReader и InputStreamReader наследуются от Reader, FileReader от InputStreamReader, а BufferedWriter, OutputStreamWriter и PrintWriter от Writer

Конструкторы BufferedReader

У класса два конструктора:

BufferedReader(Reader in) // Создает буферный поток ввода символов, который использует размер буфера по умолчанию.
BufferedReader(Reader in, int sz) // Создает буферный поток ввода символов, который использует указанный размер.

Методы BufferedReader

Методы класса:

close() // закрыть поток
mark(int readAheadLimit) // отметить позицию в потоке
markSupported() // поддерживает ли отметку потока
int read() // прочитать буфер
int read(char[] cbuf, int off, int len) // прочитать буфер
String readLine() // следующая строка
boolean ready() // может ли поток читать
reset() // сбросить поток
skip(long n) // пропустить символы
С Java 8 у BufferedReader есть еще один метод, lines(): он возвращает все строки потока как Stream<String>, и файл можно обработать без цикла while.

Пример: чтение файла

Чтение файла построчно:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderExample {

	public static void main(String[] args) {
		String inputFileName = "file.txt";

		try (BufferedReader reader = new BufferedReader(new FileReader(inputFileName))) {
			String line;
			while ((line = reader.readLine()) != null) {
				System.out.println(line + "\n");
			}
		} 
                catch (IOException e) {
			e.printStackTrace();
		}
	}

}

BufferedWriter

Java класс BufferedWriter записывает текст в поток вывода символов, буферизуя записанные символы, чтобы обеспечить эффективную запись символов, массивов и строк. Можно указать в конструкторе вторым параметром размер буфера.

Конструкторы BufferedWriter

У класса тоже два конструктора:

BufferedWriter(Writer out) // Создает буферный поток вывода символов, который использует размер буфера по умолчанию.
BufferedWriter(Writer out, int sz) // Создает буферный поток вывода символов, который использует указанный размер.

Методы BufferedWriter

Методы класса:

close() // закрыть поток
flush() // передать данные из буфера во Writer
newLine() // перенос на новую строку
write(char[] cbuf, int off, int len) // запись в буфер
write(int c) // запись в буфер
write(String s, int off, int len) // запись в буфер

Пример: запись в файл

Запись массива строк в файл:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class FileWritterExample {

	public static void main(String[] args) {
		String outputFileName = "file.txt";
		String[] array = { "one", "two", "three", "four" };

		try (BufferedWriter writter = new BufferedWriter(new FileWriter(outputFileName))) {
			for (String value : array) {
				writter.write(value + "\n");
			}
		} 
        catch (IOException e) {
			e.printStackTrace();
		}
	}

}
FileWriter сразу записывает данные на диск и каждый раз к нему обращается, а буфер работает как обертка и ускоряет работу приложения. Буфер накапливает данные в себе, а потом большим куском сбрасывает их на диск. Так же устроены и буферизованные байтовые потоки.

Считываем данные с консоли и записываем в файл

Соединим оба класса в одной программе: будем читать ввод с клавиатуры и писать его в файл, пока не встретим строку exit.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

public class ConsoleReaderExample {

	public static void main(String[] args) {
		String outputFileName = "file.txt";

		try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
			try (BufferedWriter writter = new BufferedWriter(new FileWriter(outputFileName))) {
				String line;
				while (!(line = reader.readLine()).equals("exit")) { // Прерывание цикла при написании строки exit
					writter.write(line);
				}
			}
		} 
         catch (IOException e) {
			e.printStackTrace();
		}
	}

}

Вопросы и ответы

Зачем оборачивать FileReader в BufferedReader?

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

Что возвращает readLine(), когда файл закончился?

null. Именно поэтому его удобно ставить условием цикла: while ((line = reader.readLine()) != null). Пустая строка внутри файла вернется как пустая строка, а не как null.

Почему данные не появляются в файле сразу после write()?

Потому что BufferedWriter держит их в буфере. На диск они уходят, когда буфер заполнится, либо при вызове flush() или close(). Если поток не закрыть, часть данных может пропасть.

Как задать свой размер буфера?

Вторым параметром конструктора: new BufferedReader(reader, 16384) или new BufferedWriter(writer, 16384). Если параметр не указан, берется размер по умолчанию.

Нужно ли закрывать BufferedReader и BufferedWriter вручную?

Закрывать нужно обязательно, но вручную не обязательно. Если создать поток в круглых скобках try-with-resources, как в примерах выше, он закроется сам, даже когда возникнет исключение.

Как читать ввод с консоли через BufferedReader?

Обернуть System.in в InputStreamReader, а его в BufferedReader: new BufferedReader(new InputStreamReader(System.in)). Дальше readLine() возвращает то, что пользователь ввел и подтвердил клавишей Enter. Источник: Oracle: класс BufferedReader.

Читайте также