1. Overview

As the name suggests, FileReader is a Java class that makes it easy to read the contents of a file.

In this tutorial, we’ll learn the basic concept of a Reader and how we can use the FileReader class for doing read operations on a character stream in Java.

2. Reader Basics

If we look at the code of the FileReader class, then we’ll notice that the class contains minimal code for creating a FileReader object and no other methods.

This raises questions like “Who does the heavy lifting behind this class?”

To answer this question, we must understand the concept and hierarchy of the Reader class in Java.

Reader is an abstract base class that makes reading characters possible through one of its concrete implementations. It defines the following basic operations of reading characters from any medium such as memory or the filesystem:

  • Read a single character
  • Read an array of characters
  • Mark and reset a given position in a stream of characters
  • Skip position while reading a character stream
  • Close the input stream

Naturally, all the implementations of Reader class must implement all the abstract methods, namely read() and close(). Moreover, most implementations also override other inherited methods to give additional functionality or better performance.

2.1. When to Use a FileReader

Now that we’ve some understanding about a Reader, we’re ready to bring our focus back to the FileReader class.

FileReader inherits its functionality from InputStreamReader, which is a Reader implementation designed to read bytes from an input stream as characters.

Let’s see this hierarchy in the class definitions:

public class InputStreamReader extends Reader {}

public class FileReader extends InputStreamReader {}

In general, we can use an InputStreamReader for reading characters from any input source.

However, when it comes to reading text from a file, using an InputStreamReader would be like cutting an apple with a sword. Of course, the right tool would be a knife, which is precisely what FileReader promises.

We can use a FileReader when we want to read text from a file using the system’s default character set. For any other advanced functionality, it’d be ideal for making use of InputStreamReader class directly.

3. Reading a Text File with a FileReader

Let’s walk through a coding exercise of reading characters from a HelloWorld.txt file using a FileReader instance.

3.1. Creating a FileReader

As a convenience class, FileReader offers three overloaded constructors that can be used to initialize a reader that can read from a file as an input source.

Let’s take a look at these constructors:

public FileReader(String fileName) throws FileNotFoundException {
    super(new FileInputStream(fileName));
}

public FileReader(File file) throws FileNotFoundException {
    super(new FileInputStream(file));
}

public FileReader(FileDescriptor fd) {
    super(new FileInputStream(fd));
}

In our case, we know the filename of the input file. Consequently, we can use the first constructor to initialize a reader:

FileReader fileReader = new FileReader(path);

3.2. Reading a Single Character

Next, let’s create readAllCharactersOneByOne(), a method for reading characters from the file one at a time:

public static String readAllCharactersOneByOne(Reader reader) throws IOException {
    StringBuilder content = new StringBuilder();
    int nextChar;
    while ((nextChar = reader.read()) != -1) {
        content.append((char) nextChar);
    }
    return String.valueOf(content);
}

As we can see from the above code, we’ve used the read() method in a loop to read characters one by one until it returns -1, meaning there’re no more characters to read.

Now, let’s test our code by validating that the text read from the file matches the expected text:

@Test
public void givenFileReader_whenReadAllCharacters_thenReturnsContent() throws IOException {
    String expectedText = "Hello, World!";
    File file = new File(FILE_PATH);
    try (FileReader fileReader = new FileReader(file)) {
        String content = FileReaderExample.readAllCharactersOneByOne(fileReader);
        Assert.assertEquals(expectedText, content);
    }
}

3.3. Reading of an Array of Characters

We can even read multiple characters at once using the inherited read(char cbuf[], int off, int len) method:

public static String readMultipleCharacters(Reader reader, int length) throws IOException {
    char[] buffer = new char[length];
    int charactersRead = reader.read(buffer, 0, length);
    if (charactersRead != -1) {
        return new String(buffer, 0, charactersRead);
    } else {
        return "";
    }
}

There’s a subtle difference in the return value of read() when it comes to reading multiple characters in an array. The return value here is either the number of characters read or -1 if the reader has reached the end of the input stream.

Next, let’s test the correctness of our code:

@Test
public void givenFileReader_whenReadMultipleCharacters_thenReturnsContent() throws IOException {
    String expectedText = "Hello";
    File file = new File(FILE_PATH);
    try (FileReader fileReader = new FileReader(file)) {
        String content = FileReaderExample.readMultipleCharacters(fileReader, 5);
        Assert.assertEquals(expectedText, content);
    }
}

4. Limitations

We’ve seen that the FileReader class relies on the default system character encoding.

So, for situations, where we need to use custom values for the character set, buffer size, or input stream, we must use InputStreamReader.

Moreover, we all know that I/O cycles are expensive and can introduce latency to our application. So, it’s in our best interest to minimize the number of I/O operations by wrapping a BufferedReader around our FileReader object:

BufferedReader in = new BufferedReader(fileReader);

5. Conclusion

In this tutorial, we learned about the basic concepts of a Reader and how FileReader makes it simple to do read operations on text files though some examples.

As always, the complete source code for the tutorial is available on GitHub.