File Handling using Java 8 Streams

Introduction

As we all know, Java 8 Streams can effectively be used for making data transformations/processing just like we do with SQL. When we talk about streams, there is a pipeline through which the data will flow and also the operations to act on the data. If you are new to Streams, please do check out our basic article on streams here.

Just like handling collections and arrays, working with files is also very powerful with Java 8 Streams. In this article, we shall look at this in detail.

Getting Started

Reading Line by Line

java.nio.file.Files class contains many useful static methods, that operate on files and folders. It is easy to read a file line by line by invoking Files.lines() method as shown below. Apart from the simplicity of code, an additional advantage is that the memory usage is less as you will be reading only one line at a time.

try (Stream<String> lines = Files.lines(Path.of("a.txt"))) {
lines.forEach(System.out::println);

Another way to achieve the same is using the BufferedReader.lines() method. In the below example, the elements of the stream returned by the lines() method are then collected into a String separated by a delimiter by invoking the collect() method.

BufferedReader bufferedReader = Files.newBufferedReader(Paths.get("a.txt"));
String result = bufferedReader
     .lines()
     .collect(Collectors.joining(", "));	

When using Java streams for file processing, always use the try-with-resources statement which is a try statement that ensures that the declared resources are closed at the end of the statement. For this to work, the resources declared in try() must implement the AutoCloseable interface. As Java 8 streams implement AutoCloseable, we can use try-with-resources to close the stream.

Using try-with-resources

In the below example,

try (Stream<String> lines = Files.lines(Path.of("a.txt"))) {
   lines.filter(line -> line.startsWith("A"));
} catch (IOException e) {
	e.printStackTrace();
}

Examples on File Processing

Once we read from the file, the streams can be used to manipulate them as we do with collections or arrays.

Mapping, Sorting and Collecting

In the below example, the lines read from the file are converted to uppercase, sorted in reverse order and then collected in a list.

Stream<String> lines = Files.lines(Path.of("a.txt"));
List<String> converted = lines
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());

Splitting Words

In the below example, each line in the file is converted to a stream of words using split(), and then all those streams are combined into one single stream by invoking the flatMap() method.

Path path = Path.of("a.txt");
Stream<String> lines = Files.lines(path);
Stream<String> words = lines
   .flatMap(line -> Stream.of(line.split("\\W+"))); 

Listing File/Folders

So what if you need to get a list of files or folders? The Files.list() method returns a lazily populated stream of files/folders and the list is not recursive. You can apply a filter to specify if you need files/directories as shown in the below example.

Stream<Path> paths = Files.list(Path.of("./"));
paths.filter(Files::isDirectory)
.forEach(System.out::println);

Walking across Folders

The Files.walk() method iterates over a directory to return a lazy loaded stream whose elements represent the contents of that directory. This is done recursively till the depth passed as the second argument as shown in the example below.

int depth=2;
Stream<Path> stream = Files.walk(Path.of("./"), depth));
Set<String> fileNames = stream.filter(file -> Files  .isDirectory(file))
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toSet());

Finding Files/Directories

The Files.find() method is similar to Files.walk() we saw previously, but takes an additional argument of type BiPredicate that is used to filter the files/directories. In the below example, only directories with a size less than 10 kb are returned by the find() method.

try (Stream<Path> paths = Files
  .find(Path.of("./"), 1, (path, attr) -> {
  return attr.isDirectory() && attr.size() < 10240; 
})) {
   paths.forEach((p) -> System.out.println(p));
} catch (Exception e) {
	e.printStackTrace();
}

Leave a Reply

Your email address will not be published. Required fields are marked *