Java streams 10. Create from file

Class java.nio.file.Files has the following methods that create streams: 
— Stream<String> Files.lines(Path path)
— Stream<String> Files.lines(Path path, Charset cs)
— Stream<Path> Files.list(Path dir)
— Stream<Path> walk(Path start, FileVisitOption… options)
— Stream<Path> walk(Path start, int maxDepth, FileVisitOption… options)

The lines() method creates a stream of lines read from the file specified by the path. The list() and walk() methods create streams of entries in the directory specified by the passed-in path.

Creating java.nio.file.Path

There are many ways to construct Path object. We will use the simplest one:

     
  Path filePath = 
        Path.of("src", "main", "resources", "files", "lines.txt");
   

Under “resources”, we have created directory “files” and file “lines.txt” in it as follows:

We have also created the directory “level1” under “files”, which we will use later.

The lines.txt file contains these three text lines: 

  
    
  This is line one
  This is line two
  This is line three

     

As you have probably guessed by now, we have assumed that all the following code examples are executed in the directory where “src” is a sub-directory.

Using method File.lines()

Let us now create a stream that will emit the lines from the file lines.txt:

   
  Stream<String> linesStream = Files.lines(filePath);

  

This method reads lines from the file lazily—only when the terminal operator requests the next item. A terminal operator is a method of Stream interface that completes processing of each emitted item. For example, the forEach() operator is a terminal operator:

   
  linesStream.forEach(System.out::println);
   

As you can see, the forEach() operator accepts the function (Consumer<String> interface) which is then applied to each received item.

Let’s now look at the whole code snippet:

   
  Path filePath = 
       Path.of("src", "main", "resources", "files", "lines.txt");
  try(Stream<String> linesStream = Files.lines(filePath)){
      linesStream.forEach(System.out::println);
  } catch (Exception ex){
      ex.printStackTrace();
  }
  

The output of this code is as follows:

That is what we have expected: all the lines of the file are printed out.

Now let’s discuss the usage of the try-with-resources statement. If you read the JavaDoc of any method that reads from or writes to a file, you will find that the authors recommend either close the file in a try-finally construct or to include it in a try-with-resources statement (as we have done in our example). The latter is a very convenient way to do it as it makes sure that all the exceptions listed in the catch block are caught and that the file, opened by the stream, is closed afterward (exactly how the try-finally construct).

In order to use the try-with-resources statement, the created resource should implement the AutoClosable interface:

    
  public interface AutoCloseable {
      void close() throws Exception;
  }
   

And here is how the Stream interface and its parents are related to the AutoClosable:

  
  public interface Stream<T> extends BaseStream<T, Stream<T>> {
    ...
  }

  public interface BaseStream<T, BaseStream<T, S>> extends AutoCloseable {
   ...
  }

Which means that the Stream interface extends AutoCloseable and can be used in the try-with-resources statement.

Now, let us discuss the overloaded version of the lines() method:

  
  Stream<String> Files.lines(Path path, Charset cs)
  

Adding a Charset as a parameter allows to map the sequences of bytes (from the file) to the sixteen-bit Unicode code units. Computers “understand” numbers only. To interpret them as a text, the numbers have to be converted into the text characters, using some scheme, called character set or charset.

By default, the lines() method uses StandardCharsets.UTF_8 (Eight-bit UCS Transformation Format). The overloaded version allows to specify a different Charset. For example, the following code produces the same output as the previous example (with the default charset):

  
  Charset cs = StandardCharsets.US_ASCII;
  try(Stream<String> linesStream = Files.lines(filePath, cs)){
      linesStream.forEach(System.out::println);
  } catch (Exception ex){
      ex.printStackTrace();
  }
   

But changing to StandardCharsets.UTF_16BE (Sixteen-bit UCS Transformation Format, big-endian byte order), changes the output (interprets the input bytes differently):

The overloaded lines() method is especially useful for creating decoders/encoders or just for reading data using various charsets.

Using method File.list(Path)

This method returns a lazily populated Stream<Path> that emits entries of the specified directory. If the passed-in path does not represent a directory, it throws NotDirectoryException. To demonstrate usage of this method, let’s print all the entries of the directory “files”:

  
  Path dir = Path.of("src", "main", "resources", "files");
  try(Stream<Path> dirStream = Files.list(dir)){
      dirStream.forEach(System.out::println);
  } catch (NotDirectoryException ex){
      System.out.println("The " + dir + " is not a directory.");;
  } catch (Exception ex){
      ex.printStackTrace();
  }
   

As you can see, we have created a path that represents the directory “files.” The output is as follows:

As was expected, the output includes all the files and sub-directories of the specified directory.

Using method File.walk()

This method creates a lazily populated Stream<Path> object that emits entries of the specified directory. It has two overloaded versions: 
— Stream<Path> walk(Path start, FileVisitOption… options)
— Stream<Path> walk(Path start, int maxDepth, FileVisitOption… options)

The varargs of the FileVisitOptions enum values specify how the directory has to be traversed. At the time of this writing, there is only one option FileVisitOptions.FOLLOW_LINKS in the FileVisitOptions enum. It instructs the walk() method to visit all the links in the directory. To demonstrate how it works, we have created under “files” several nested directories (“level1”, “level2”, and “level3”), each containing a .txt file:

Now let’s walk the directory “files”:

  
  Path dir = Path.of("src", "main", "resources", "files");
  try(Stream<Path> dirStream = Files.walk(dir, 
  FileVisitOption.FOLLOW_LINKS)){
      dirStream.forEach(System.out::println);
  } catch (NotDirectoryException ex){
      System.out.println("The " + dir + " is not a directory.");;
  } catch (Exception ex){
      ex.printStackTrace();
  }
   

The output is as follows:

As you can see, the method has traversed all the file tree starting from the specified directory.

The overloaded version of the walk() method allows to limit the depth of the file tree to be traversed. For example, let’s set the max depth to 2:

  
  Path dir = Path.of("src", "main", "resources", "files");
  try(Stream<Path> linesStream = Files.walk(dir, 2, 
  FileVisitOption.FOLLOW_LINKS)){
      linesStream.forEach(System.out::println);
  } catch (NotDirectoryException ex){
      System.out.println("The " + dir + " is not a directory.");;
  } catch (Exception ex){
      ex.printStackTrace();
  }
   

The output is as follows:

As expected, the output now shows only two levels of the file tree below the “files” directory. 

This concludes our overview of the java.nio.file.Files methods that create streams.

In the next post, we will talk about creating a stream from String object using methods 
— IntStream chars()
— IntStream codePoints()
— Stream<String> lines()

See other posts on Java 8 streams and posts on other topics.
You can also use navigation pages for Java stream related blogs:
— Java 8 streams blog titles
— Create stream
— Stream operations
— Stream operation collect()
The source code of all the code examples is here in GitHub

, ,

Send your comments using the link Contact or in response to my newsletter.
If you do not receive the newsletter, subscribe via link Subscribe under Contact.

Powered by WordPress. Designed by Woo Themes