Java Character Streams: Reader and Writer - Quiz

Total: 6 questions

1. 

What are the Reader and Writer classes in Java, and what are their main methods?

Reader and Writer are abstract classes of the java.io package that all character streams extend: input streams extend Reader, output streams extend Writer. They work with text as char values (16-bit UTF-16 units) rather than as bytes.

  • Reader.read() returns the character code as an int from 0 to 65535, or -1 at the end of the stream; read(char[]) returns the number of characters actually read.
  • Writer.write() writes a character, an array or a string; append() does the same and returns the same Writer, so calls can be chained.
  • Writer.flush() pushes buffered characters to the destination; close() in both classes closes the stream (a Writer flushes its buffer first).

On an I/O error the methods throw IOException. The exception is PrintWriter, which reports errors through checkError().

2. 

Why can FileReader read text as garbled characters, and how do you set the charset?

A file holds bytes, and FileReader converts them to characters using a charset. The constructor new FileReader("a.txt") without a Charset parameter uses the default charset; up to and including Java 17 it depended on the OS, and on Windows with a US or Western European locale it is windows-1252. A UTF-8 file where é takes two bytes is read as café instead of «café». No exception is thrown — the bug shows up only in the data.

The right way:

  • since Java 11 — new FileReader("a.txt", StandardCharsets.UTF_8) and new FileWriter("a.txt", StandardCharsets.UTF_8);
  • before Java 11 — new InputStreamReader(new FileInputStream("a.txt"), StandardCharsets.UTF_8), and OutputStreamWriter for writing.

Specify the charset explicitly on both sides: when writing and when reading.

3. 

What does the readLine() method of BufferedReader return, and how do you read a file line by line correctly?

readLine() returns the next line without the line terminator (\n, \r or \r\n — it understands all three) and null when the file is over. An empty line in the file comes back as "", not null; the last line without a line break is read too.

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

The only end-of-file signal is null. The loop while (!(line = reader.readLine()).isEmpty()) stops at the first empty line and throws a NullPointerException at the end of the file. Since Java 8 you can also get the lines as a Stream: reader.lines().

4. 

How do you append text to the end of an existing file instead of overwriting it?

Pass append = true as the second argument of the FileWriter constructor: new FileWriter("log.txt", true). Characters are then added to the end of the file, and if the file does not exist, it is created. With an explicit charset (Java 11+): new FileWriter("log.txt", StandardCharsets.UTF_8, true).

The constructors new FileWriter("log.txt"), new PrintWriter("log.txt") and new PrintWriter(new File("log.txt")) create the file or truncate an existing one, so a log opened with them loses its old entries on every run. To write through a PrintWriter in append mode, wrap a FileWriter: new PrintWriter(new FileWriter("log.txt", true)).

5. 

Does PrintWriter throw IOException, and how do you find out about a write error?

The writing methods of PrintWriter (print(), println(), printf(), format(), write()) do not throw IOException. If the disk is full or the stream is already closed, the exception is caught internally and an error flag is set. The boolean checkError() method flushes the stream and returns true if any write has failed.

PrintWriter pw = new PrintWriter(new StringWriter());
pw.close();
pw.println("after close");           // no exception
System.out.println(pw.checkError()); // true

Exceptions can come only from constructors: PrintWriter(String) and PrintWriter(File) throw FileNotFoundException, and PrintWriter(String, Charset) declares IOException. Where data loss is unacceptable, call checkError() after writing or use BufferedWriter, which throws IOException.

6. 

Why do you need flush(), and when does PrintWriter flush its buffer automatically?

flush() pushes the characters collected in the buffer to the target stream (a file, a socket, another Writer). Buffering is not limited to BufferedWriter (8192 characters): even FileWriter keeps bytes in the internal buffer of its encoder, and without flush() or close() they never reach the file. close() calls flush() itself before closing.

PrintWriter(Writer) and PrintWriter(OutputStream) do not flush automatically by default. The constructors with autoFlush = true flush the buffer after every println(), printf() and format(), but not after print() or write(). So when talking over a socket, create the PrintWriter with autoFlush = true and send messages with println(); otherwise the line stays in the buffer and the other side never receives it.

Page 1 of 1