Классы символьных потоков в Java: Reader и Writer - Вопросы
Всего: 6 вопросов
1. Что такое классы Reader и Writer в Java и какие у них основные методы?
Что такое классы 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 может прочитать русский текст «кракозябрами» и как указать кодировку?
В файле лежат байты, и 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() класса 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 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() и когда 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(), иначе строка останется в буфере, и собеседник её не получит.