Классы символьных потоков в Java: Reader и Writer - Вопросы

Всего: 6 вопросов

1. 

Что такое классы Reader и Writer в Java и какие у них основные методы?

Reader и Writer — абстрактные классы пакета java.io, от которых наследуются все символьные потоки: от Reader — потоки чтения, от Writer — потоки записи. Они работают с текстом символами char (16-битными единицами UTF-16), а не байтами.

  • Reader.read() возвращает код символа как int от 0 до 65535 или -1 в конце потока; read(char[]) возвращает число реально прочитанных символов.
  • Writer.write() записывает символ, массив или строку; append() делает то же и возвращает этот же Writer, поэтому вызовы можно объединять в цепочку.
  • Writer.flush() выталкивает накопленные символы в приёмник, close() у обоих классов закрывает поток (у Writer — предварительно сбросив буфер).

При ошибке ввода-вывода методы выбрасывают IOException. Исключение — PrintWriter, который сообщает об ошибках через checkError().

2. 

Почему FileReader может прочитать русский текст «кракозябрами» и как указать кодировку?

В файле лежат байты, и FileReader переводит их в символы по кодировке. Конструктор new FileReader("a.txt") без параметра Charset берёт кодировку по умолчанию; до Java 17 включительно она зависела от ОС, и под русской Windows это windows-1251. Файл в UTF-8, где буква кириллицы занимает 2 байта, читается как Привет вместо «Привет». Исключения при этом нет — ошибку видно только по данным.

Как правильно:

  • с Java 11 — new FileReader("a.txt", StandardCharsets.UTF_8) и new FileWriter("a.txt", StandardCharsets.UTF_8);
  • до Java 11 — new InputStreamReader(new FileInputStream("a.txt"), StandardCharsets.UTF_8) и OutputStreamWriter для записи.

Кодировку нужно указывать явно с обеих сторон: и при записи, и при чтении.

3. 

Что возвращает метод readLine() класса BufferedReader и как правильно читать файл построчно?

readLine() возвращает очередную строку без символов конца строки (\n, \r или \r\n — понимает все три варианта) и null, когда файл закончился. Пустая строка в файле приходит как "", а не null; последняя строка без перевода строки тоже читается.

try (BufferedReader reader = new BufferedReader(
        new FileReader("a.txt", StandardCharsets.UTF_8))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

Признак конца файла — только null. Цикл while (!(line = reader.readLine()).isEmpty()) оборвётся на первой пустой строке, а в конце файла выбросит NullPointerException. С Java 8 строки можно получить и как Stream: reader.lines().

4. 

Как дописать текст в конец существующего файла, а не перезаписать его?

Передать в конструктор FileWriter второй параметр append = true: new FileWriter("log.txt", true). Тогда символы добавляются в конец файла, а если файла нет, он будет создан. С явной кодировкой (Java 11+) — new FileWriter("log.txt", StandardCharsets.UTF_8, true).

Конструкторы new FileWriter("log.txt"), new PrintWriter("log.txt") и new PrintWriter(new File("log.txt")) создают файл или обнуляют существующий, поэтому журнал, открытый ими, при каждом запуске теряет старые записи. Чтобы писать через PrintWriter в режиме добавления, оборачивают FileWriter: new PrintWriter(new FileWriter("log.txt", true)).

5. 

Выбрасывает ли PrintWriter IOException и как узнать об ошибке записи?

Методы записи PrintWriter (print(), println(), printf(), format(), write()) IOException не выбрасывают. Если диск заполнен или поток уже закрыт, исключение перехватывается внутри и выставляется флаг ошибки. Метод boolean checkError() сбрасывает буфер и возвращает true, если при какой-либо записи произошла ошибка.

PrintWriter pw = new PrintWriter(new StringWriter());
pw.close();
pw.println("после close");          // исключения нет
System.out.println(pw.checkError()); // true

Исключения возможны только из конструкторов: PrintWriter(String) и PrintWriter(File) выбрасывают FileNotFoundException, PrintWriter(String, Charset) объявляет IOException. Там, где потеря данных недопустима, вызывайте checkError() после записи или используйте BufferedWriter, который бросает IOException.

6. 

Зачем нужен flush() и когда PrintWriter сбрасывает буфер автоматически?

flush() выталкивает накопленные в буфере символы в целевой поток (файл, сокет, другой Writer). Буфер есть не только у BufferedWriter (8192 символа): даже FileWriter копит байты во внутреннем буфере кодировщика, и без flush() или close() они не попадут в файл. close() сам вызывает flush() перед закрытием.

PrintWriter(Writer) и PrintWriter(OutputStream) по умолчанию автоматически не сбрасывают данные. Конструкторы с autoFlush = true сбрасывают буфер после каждого println(), printf() и format(), но не после print() и write(). Поэтому при обмене через сокет создавайте PrintWriter с autoFlush = true и отправляйте сообщения через println(), иначе строка останется в буфере, и собеседник её не получит.

Страница 1 из 1