File IO: The Rundown

Nov 20, 2019
hackajob Staff

As you might already know, at hackajob, we’re big fans of the improvements made in Java 8. In this article, we’re going to be exploring some of the improvements made to File IO, including reading and writing a file, as well as listing the contents of a directory.

Reading a File

Before Java 8

Prior to Java 7 some of the classes available to read files included 'BufferedReader', 'FileReader' and 'Scanner'. The disadvantage of these? You need to write a ‘while loop’ to reach each line from the file, which can be pretty time consuming.

Java 7 added the ‘java.nio.file.Files’ class which has several utility methods for performing all-kinds of File IO operations. This was a pretty handy workaround and some of the methods in the class such as ‘Files.readAllBytes’ and ‘Files.readAllLines’ can both be used for reading a file. It’s key to note that these methods don’t require a ‘while loop’ and instead help to directly read the contents of a file into a ‘byte array’ or ‘String List’ respectively.

The Java 8 Way

Java 8 has added some new methods to the ‘Files’ class that help to read a file as a whole. Having also added overloaded versions of some of the existing methods, we’ve explained these improvements in more detail below:

Files.lines

The ‘Files.lines’ method returns the content of an input file as a ‘Stream’ of ‘String’ value. As we mentioned earlier, the ‘java.nio.file.Files’ class already had a method called ‘readAllLines’ that returns a ‘String’ list consisting of the lines in the input file. The advantage of the ‘Files.lines’ method is that it returns a ‘Stream’ which is populated lazily as the ‘Stream’ is consumed.

The following code demonstrates the ‘Files.lines’ method:

String fileName = "F:/fruits.txt";

Stream<String> filesStream = Files.lines(Paths.get(fileName));

filesStream.forEach(str -> System.out.print(str)+” “);

filesStream.close();

Here, the ‘Files.lines’ method is passed via a ‘Path’ instance corresponding to the ‘F:/fruits.txt’ file. After reading the file and returning a ‘String’ stream, the ‘forEach’ method is then used to iterate through the ‘Stream’ and print the contents of the file.

Suppose the 'fruits.txt' file is as below:

The above snippet of code will then print the following output:

apple  mango  banana  orange

An overloaded version of the ‘Files.lines’ method is also available. This method accepts a 'Charset parameter' in addition to the input file path, with the 'Charset parameter' also specifying the charset (UTF-8, UTF-18, etc.) that can be used for decoding the input file.

Files.newBufferedReader

The ‘Files.newBufferedReader’ method can be used to obtain a ‘BufferedReader’ which can then be used to read a file. An overloaded version of the existing method that doesn’t accept a ‘charset parameter', ‘bytes’ from the file are decoded using the ‘UTF-8 charset’. This method returns a ‘BufferedReader’ corresponding to the input path specified.

The following code demonstrates how this works:

String fileName = "F:/fruits.txt";

BufferedReader reader = Files.newBufferedReader(Paths.get(fileName));

String line = new String();

while ((line = reader.readLine()) != null) {   System.out.println(line);

}

In the example above, the ‘Files.newBufferedReader’ returns a ‘BufferedReader’ corresponding to the ‘F:/fruits.txt’ path. A ‘while loop’ is then used which enlists the ‘BufferedReader’ to print each line from the input file. Note that the ‘while’ part of the code is what you normally have to do with a ‘BufferedReader’ and isn’t something added by Java 8.

This particular code will print the same output as before.

Files.readAllLines

This method can be used to read the contents of a file into a ‘String’ list. An overloaded version of the existing Files.readAllLines method that doesn’t accept a ‘Charset parameter', ‘bytes’ from the file are decoded using the ‘UTF-8 charset’. In fact, this method returns the contents of the input file as a ‘String’ list.

The following code demonstrates how this works:

String fileName = "F:/fruits.txt";

List<String> fruitsList =  Files.readAllLines(Paths.get(fileName));

fruitsList.forEach(str -> System.out.println(str));

In the example above, the ‘Files.readAllLines’ returns a ‘String’ list corresponding to the data in ‘F:/fruits.txt’. When this code is executed, it will print the same output as before.

BufferedReader.lines

The ‘BufferedReader.lines’ method returns the content of an input file as a ‘Stream’ of ‘String’ values and is quite similar to the ‘Files.lines’ method explained above.

The following code demonstrates this method and how it works:

String fileName = "F:/fruits.txt";

BufferedReader br = new BufferedReader(new FileReader(new File(fileName)));

Stream<String> filesStream = br.lines();

filesStream.forEach(str -> System.out.print(str+" "));

filesStream.close();

A new ‘BufferedReader’ instance is obtained corresponding to the ‘F:/fruits.txt’ file. The ‘BufferedReader.lines’ method is invoked which returns a ‘String Stream’ corresponding to the data in the input file. When this code is executed, it will print the same output as before.


Writing to a File

Prior to Java 7 some of the classes available for file writing were ‘BufferedWriter’, ‘FileWriter’ and more. The ‘java.nio.file.Files’ class added by Java 7 has a method called write which can be used to write to a file. Java 8 has added a version of this method as explained below:

Files.write

This method can be used to write text to a file, with characters encoded into bytes using the 'UTF-8 charset'. The Java 8 version of this method accepts an additional ‘Charset parameter'.

The following code demonstrates this method:

Path path = Paths.get("f:/fruits.txt");

String fruits = "apple mango banana orange";

Files.write(path, fruits.getBytes());

Above, the ‘files.write’ method creates a new file corresponding to ‘f:/fruits.txt’ and writes the ‘String’ specified to this file.

Listing the Contents of a Directory

Before Java 8

Prior to Java 8, there were several ways in which you could list the contents of a directory. Instead, you could use the ‘File.list’ method combined with ‘FileNameFilter’ to filter the results. Note that the ‘java.nio.file.Files’ class added by Java 7 also has methods like ‘Files.walkFileTree’ which can be used to list the contents of a directory.

The Java 8 Way

It’s key to note that Java 8 has added further methods to the ‘java.nio.file.Files’ class that also help in listing the contents of a directory - we’ve explained some of these for you below:

Files.list

The ‘Files.list’ method can be used to obtain a list of all the files/folders at the specified location. Note that it returns a ‘Path Stream’ and that the list is not recursive. The ‘java.io.File.list’ or ‘java.io.File.listFiles()’ methods available pre-Java 8 return a string file array respectively. Remember that the advantage of the ‘Files.list’ method is that it returns a lazily populated ‘Stream’.

The following code showcases this method and how it works:

String directoryPath = "f:/testfolder";

Stream<Path> filesList = Files.list(Paths.get(directoryPath));

filesList.forEach(path -> System.out.println(path));

filesList.close();

This code lists all the files/folders at the ‘F:/testfolder’ location. Also, as mentioned earlier, the list is not recursive, so if there is a directory in the input path, the files within this directory will not be returned.

Suppose the ‘F:/testfolder’ has the following folders:

When the code is executed, it’ll print the following output:

f:\testfolder\folder1

f:\testfolder\Test1.java

f:\testfolder\test1a.txt

f:\testfolder\test1b.txt

Since the ‘files.list’ returns a ‘Stream’, all ‘Stream’ operations like ‘filter’ and ‘map’ can be applied on the resultant stream. The following code demonstrates the ‘filter’ operation to list only the ‘.java’ files in the input directory:

Stream<Path> filesList = Files.list(Paths.get(directoryPath));

filesList

.filter(path -> path.toString().endsWith(".java"))

.forEach(path -> System.out.println(path));

The above code will print the following output:

f:\testfolder\Test1.java

Files.walk

The ‘Files.walk’ method is similar to the ‘Files.list’ method and can be used to obtain a List of all the files and folders at the specified location. However, unlike the ‘Files.list’ method, the ‘Files.walk’ method works recursively and also lists the files/directories in any sub-directories in the input path. Just like ‘Files.list’, ‘Files.walk’ also returns a ‘Path Stream’.

The following code demonstrates the ‘Files.walk’ method:

String fileName = "f:/testfolder";

Stream<Path> filesStream = Files.walk(Paths.get(fileName));

filesStream.forEach(str -> System.out.println(str));

filesStream.close();

The above example lists all the files/folders at the ‘F:/testfolder’ location. Note that if there are any subfolders within a folder, their contents will also get listed.

Suppose the 'F:/testfolder' is follows:

And folder1 is like so:

When the code above is executed, it will print the following output:

f:\testfolder

f:\testfolder\folder1

f:\testfolder\folder1\folder2

f:\testfolder\folder1\Test2.java

f:\testfolder\folder1\test2.txt

f:\testfolder\Test1.java

f:\testfolder\test1a.txt

f:\testfolder\test1b.txt

There’s also an overloaded version of the ‘Files.walk’ method that accepts an additional parameter corresponding to the depth of the traversal. The following code demonstrates this:

Stream<Path> filesStream = Files.walk(Paths.get(fileName),1);

So this code will print the following output:

f:\testfolder

f:\testfolder\folder1

f:\testfolder\Test1.java

f:\testfolder\test1a.txt

f:\testfolder\test1b.txt

Files.find

The ‘Files.find’ method can be used to get a list of files at the specified location. In addition to the input file path, the ‘Files.find’ method also accepts the following parameters:

·       maxDepth – This is an ‘integer parameter’ that specifies the traversal depth

·       matcher – This is a ‘BiPredicate’ instance that specifies a condition. Only the file names that match the specified condition are returned. The following code shows how this works:

String fileName = "f:/testfolder";

Stream<Path> filesStream = Files.find(Paths.get(fileName), 2, (path,basicFileAttribute) -> basicFileAttribute.isDirectory());

filesStream.forEach(str -> System.out.println(str));

filesStream.close();

So this code lists all the files/folders at the ‘F:/testfolder’ location. The value 2 is specified for depth of traversal. Also a ‘BiPredicate’ instance is specified that checks each path in the result Stream and returns only those paths that correspond to folders. So this code prints the following output:

f:\testfolder

f:\testfolder\folder1

F:\testfolder\folder1\folder2

In this article, we saw some of the improvements made by Java 8 on the Files class. The new methods added by Java 8 take advantage of the Stream API and improve File IO operations, making them something worth learning and adding to your overall Java repertoire.

Like what you’ve read? Make sure to check out our other articles on all-things Java.