Searching for Directory Entries – Java I/O: Part II

Searching for Directory Entries

The find() method of the Files class can be used for searching or finding directory entries in the file system.

Click here to view code image

static Stream<Path> find(Path start, int depth,
                         BiPredicate<Path,BasicFileAttributes> matcher,
                         FileVisitOption… options) throws IOException

Returns a Stream that is lazily populated with Path objects by searching for entries in a directory hierarchy rooted at the Path object denoted by the start parameter. The start parameter can be a directory or a file.

The method walks the directory hierarchy in exactly the same manner as the walk() method. The methods traverse the entries depth-first to a depth limit that is the minimum of the actual depth of the directory hierarchy rooted at the start entry and the depth that is specified.

The matcher parameter defines a BiPredicate that is used to decide whether a directory entry should be included in the stream. For each directory entry in the stream, the BiPredicate is invoked on its Path and its BasicFileAttributes— making it possible to define a customized filter.

This method does not follow symbolic links, unless the constant FileVisitOption.FOLLOW_LINKS is specified.

The find() method is analogous to the walk() method in many respects: It traverses the directory hierarchy in the same way, does not follow symbolic links by default, follows symbolic links only if the constant FileVisitOption.FOLLOW_LINKS is specified, and monitors visiting entries to detect cyclic path dependencies since the depth limit is always specified.

Whereas both walk() and find() methods create a stream of Path objects, the find() method also allows a matcher for the search to be defined by a lambda expression that implements the BiPredicate<Path, BasicFileAttributes> functional interface. This matcher is applied by the method and determines whether an entry is allowed in the stream. This is in contrast to an explicit intermediate filter operation on the stream, as in the case of a stream created by the walk() method. The find() method supplies the BasicFileAttributes object associated with a Path—analogous to using the readAttributes() method of the Files class (p. 1328). Given the Path object and its associated BasicFileAttributes object with the basic set of read-only file attributes, it is possible to write complex search criteria on a directory entry. The methods of the BasicFileAttributes interface also do not throw checked exceptions, and are therefore convenient to call in a lambda expression, as opposed to corresponding methods of the Files class.

In the following code, we use the find() method to find regular files whose name ends with “.txt” and whose size is greater than 0 bytes. The BiPredicate at (1) defines the filtering criteria based on querying the Path object in the stream for its file extension and its associated BasicFileAttributes object with read-only file attributes for whether it is a regular file and for its size.

Click here to view code image

System.out.println(“Find regular files whose name ends with \”.txt\””
                 + ” and whose size is > 0:”);
Path startEntry = Path.of(“.”, “a”);
int depth = 5;
try (var pStream = Files.find(startEntry, depth,
                              (path, attrs) -> attrs.isRegularFile()       // (1)
                              && attrs.size() > 0
                              && path.toString().endsWith(“.txt”))) {
   List<Path> pList = pStream.toList();
   System.out.println(pList);
} catch (IOException ioe) {
  ioe.printStackTrace();
}

Output from the code:

Click here to view code image

Find regular files whose name ends with “.txt” and whose size is > 0:
[./a/x.txt, ./a/b/c/z.txt, ./a/b/d/y.txt]

Note that the method find() requires the depth of the search to be specified. However, the actual depth traversed is always the minimum of the maximum depth of the directory hierarchy and the depth specified. The maximum depth of the hierarchy rooted at directory a is 3 and the specified depth is 5. The actual depth traversed in the directory hierarchy is thus 3. In the code above, different values for the depth can give different results.

If the constant FileVisitOption.FOLLOW_LINKS is specified in the find() method, its behavior is analogous to the behavior of the walk() method. It will keep track of the directories visited, and any cyclic path dependency encountered will unceremoniously result in a FileSystemLoopException. The curious reader is encouraged to experiment with the code above by specifying the constant FileVisitOption.FOLLOW_LINKS and passing different values for the depth in the find() method call.

Leave a Reply

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