Java 8 reads, writes, traverses and monitors files and directories

Posted by activeradio on Wed, 09 Feb 2022 07:27:26 +0100

JavaDoc: https://javadocs.techempower.com/

1, Read file

Reference link: https://howtodoinjava.com/java8/read-file-line-by-line-in-java-8-streams-of-lines-example/

In the following example, the file will be read line by line using Stream. The file data to be read Txt reads as follows:

Never
store
password
except
in mind.

Read the above file line by line. If one line contains password, print this line

1. Java 8 Stream reads files by line

Use Java 8 Stream to read the file content by line, filter the line containing password, and take out the first one to print

private static void readStreamOfLinesUsingFiles() throws IOException
{
    Stream<String> lines = Files.lines(Paths.get("c:/temp", "data.txt"));
 
    Optional<String> hasPassword = lines.filter(s -> s.contains("password")).findFirst();
 
    if(hasPassword.isPresent())
    {
        System.out.println(hasPassword.get());
    }
 
    //Close the stream and it's underlying file as well
    lines.close();
}

2. FileReader reads files by line

Until java7, you can use the following methods to read files. There are many implementation methods, but they are not the focus of this article. They are just compared with the above examples

private static void readLinesUsingFileReader() throws IOException
{
    File file = new File("c:/temp/data.txt");
 
    FileReader fr = new FileReader(file);
    BufferedReader br = new BufferedReader(fr);
 
    String line;
    while((line = br.readLine()) != null)
    {
        if(line.contains("password")){
            System.out.println(line);
        }
    }
    br.close();
    fr.close();
}

3. The try with resources mode automatically closes the flow

The first example can meet our needs of reading files line by line in the application, but if you want to make it better, we can use the try with resources method, which will save the operation of closing the stream close() at last.

private static void readStreamOfLinesUsingFilesWithTryBlock() throws IOException
{
    Path path = Paths.get("c:/temp", "data.txt");
 
    //The stream hence file will also be closed here
    try(Stream<String> lines = Files.lines(path))
    {
        Optional<String> hasPassword = lines.filter(s -> s.contains("password")).findFirst();
 
        if(hasPassword.isPresent()){
            System.out.println(hasPassword.get());
        }
    }
}

When a Stream generates another Stream, the close method will be called in a chain (the Stream below it will also be called). You can write it in the following way:

private static void readStreamOfLinesUsingFilesWithTryBlock() throws IOException
{
    Path path = Paths.get("c:/temp", "data.txt");
 
    //When filteredLines is closed, it closes underlying stream as well as underlying file.
    try(Stream<String> filteredLines = Files.lines(path).filter(s -> s.contains("password")))
    {
        Optional<String> hasPassword = filteredLines.findFirst();
 
        if(hasPassword.isPresent()){
            System.out.println(hasPassword.get());
        }
    }
}

If you want to test whether the Stream below it triggers close, you can use onClose to test it

private static void readStreamOfLinesUsingFilesWithTryBlock() throws IOException
{
    Path path = Paths.get("c:/temp", "data.txt");
    //When filteredLines is closed, it closes underlying stream as well as underlying file.
    try(Stream<String> filteredLines = Files.lines(path)
                                    //test if file is closed or not
                                    .onClose(() -> System.out.println("File closed"))
                                    .filter(s -> s.contains("password"))){
        Optional<String> hasPassword = filteredLines.findFirst();
        if(hasPassword.isPresent()){
            System.out.println(hasPassword.get());
        }
    }
}

output

password
File closed

2, Write file

Reference link: https://howtodoinjava.com/java8/java-8-write-to-file-example/

1. Java 8 uses BufferedWriter to write files

BufferedWriter is used to write text to a character or byte stream. Before printing characters, it stores them in a buffer and prints them in bulk. If there is no buffer, every call to the print () method will cause characters to be converted into bytes and then written to the file immediately, which will be very inefficient.

//Get the file reference
Path path = Paths.get("c:/output.txt");
 
//Use try-with-resource to get auto-closeable writer instance
try (BufferedWriter writer = Files.newBufferedWriter(path))
{
    writer.write("Hello World !!");
}

2. Use files Write() write to file

Use files Write () is also beautiful and concise

String content = "Hello World !!";
 
Files.write(Paths.get("c:/output.txt"), content.getBytes());

3, Traverse iteration directory

Reference link: https://howtodoinjava.com/java8/java-8-list-all-files-example/

Learn to use Java 8 API s, such as files List () and DirectoryStream, and recursively list all files existing in the directory, including hidden files.

For using external iterations( For circulation ), use DirectoryStream. To use Stream API For operations (mapping, filtering, sorting, collection), see files List() instead.

1. Files.list () – traverses all files and subdirectories

Files. The list () method traverses all files and subdirectories in the current directory

Files.list(Paths.get("."))
        .forEach(System.out::println);
// perhaps
List<File> files = Files.list(Paths.get(dirLocation))
                        .map(Path::toFile)
                        .collect(Collectors.toList());
files.forEach(System.out::println);
 Output:
 
.\filename1.txt
.\directory1
.\filename2.txt
.\Employee.java

2. Files.list () – lists only files that do not include subdirectories

Use the filter expression Files::isRegularFile to check whether the file is an ordinary file to filter out subdirectories, retain the file and print

Files.list(Paths.get("."))
        .filter(Files::isRegularFile)
        .forEach(System.out::println);
// perhaps
List<File> files = Files.list(Paths.get("."))
                .filter(Files::isRegularFile)
                .map(Path::toFile)
                .collect(Collectors.toList());
files.forEach(System.out::println);
Output:

.\filename1.txt
.\filename2.txt
.\Employee.java

3. Files.newDirectoryStream() – lists all files and subdirectories

Java provides a more flexible way to traverse directory content: files newDirectoryStream()

Note that if you operate on a large directory, using DirectoryStream will speed up the code

Files.newDirectoryStream(Paths.get("."))
        .forEach(System.out::println);
Output:
.\filename1.txt
.\directory1
.\filename2.txt
.\Employee.java

4. Files.newDirectoryStream() – iterates only files that do not contain subdirectories

Only traverse files, exclude directories, and control through the second parameter of path filter

Files.newDirectoryStream(Paths.get("."), path -> path.toFile().isFile())
        .forEach(System.out::println);
Output:

.\filename1.txt
.\filename2.txt
.\Employee.java

5. List only all documents within a certain scope

To get a list of all files with only some extensions, use both the predicates Files::isRegularFile and path toString(). endsWith(".java").

Using the above predicates, we will list All files in the java folder.

Files.newDirectoryStream(Paths.get("."),
        path -> path.toString().endsWith(".java"))
        .forEach(System.out::println);
// perhaps
List<File> files = Files.list(Paths.get(dirLocation))
                                    .filter(Files::isRegularFile)
                                    .filter(path -> path.toString().endsWith(".java"))
                                    .map(Path::toFile)
                                    .collect(Collectors.toList());
files.forEach(System.out::println);
Output:

.\Employee.java

6. Find all hidden files in the directory

To find all hidden files, you can file - > file Ishidden() uses a filter expression in any of the above examples.

final File[] files = new File(".").listFiles(file -> file.isHidden());
// Or method reference
final File[] files = new File(".").listFiles(File::isHidden);
// Or use the Files utility class
List<File> files = Files.list(Paths.get("."))
            .filter(path -> path.toFile().isHidden())
            .map(Path::toFile)
            .collect(Collectors.toList());

4, Monitor changes in directories, subdirectories, and files

Reference link: https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/

Here, you will use the Java 7 WatchService API to observe the directory and all subdirectories and files in it. WatchService is jdk1 7 version, located in Java nio. File package. WatchService is based on the local operating system to monitor files.

1. How to register WatchService

To register WatchService, get the directory path and use path Register() method.

Path path = Paths.get(".");
WatchService watchService =  path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

2. Observe change events

To get changes to the directory and its files, use watchkey Pollevents() method, which returns a collection of all change events as a stream.

WatchKey watchKey = null;
while (true) {
    watchKey = watchService.poll(10, TimeUnit.MINUTES);
    if(watchKey != null) {
        watchKey.pollEvents().stream().forEach(event -> System.out.println(event.context()));
    }
    watchKey.reset();
}

The key remains valid until:

  • Explicitly cancel it by calling its cancel method, or
  • Implicitly canceled because the object is no longer accessible, or
  • By turning off the watch service.

If you want to use the same key multiple times in the loop to get the change event, don't forget to call watchkey Reset() method to reset the key to the ready state.

Note that several things, such as how to detect events, their timeliness, and whether to preserve their order, are highly dependent on the underlying operating system. Some changes may result in a single entry in one operating system, while similar changes may result in multiple events in another operating system.

3. Monitor examples of changes in directories, subdirectories and files

In this example, we will see an example of a viewing directory that contains all subdirectories and files. We will maintain the mapping between monitoring keys and directories. Map < watchkey, Path > keys to correctly identify the modified directories.

The following method will register a path with the observer, and then store the path and key in the map.

private void registerDirectory(Path dir) throws IOException 
{
    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
    keys.put(key, dir);
}

This method is called recursively when traversing the directory structure and calling this method for each directory encountered.

private void walkAndRegisterDirectories(final Path start) throws IOException {
    // register directory and sub-directories
    Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            registerDirectory(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}

Please note that whenever we create a new directory, we will register the directory in watchservice and add the new key to the map.

WatchEvent.Kind kind = event.kind();
if (kind == ENTRY_CREATE) {
    try {
        if (Files.isDirectory(child)) {
            walkAndRegisterDirectories(child);
        }
    } catch (IOException x) {
        // do something useful
    }
}

Put all the above contents together with the logic of handling events. The complete example is as follows:

import static java.nio.file.StandardWatchEventKinds.*;
 
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
 
public class Java8WatchServiceExample {
 
    private final WatchService watcher;
    private final Map<WatchKey, Path> keys;
 
    /**
     * Creates a WatchService and registers the given directory
     */
    Java8WatchServiceExample(Path dir) throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey, Path>();
 
        walkAndRegisterDirectories(dir);
    }
 
    /**
     * Register the given directory with the WatchService; This function will be called by FileVisitor
     */
    private void registerDirectory(Path dir) throws IOException 
    {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        keys.put(key, dir);
    }
 
    /**
     * Register the given directory, and all its sub-directories, with the WatchService.
     */
    private void walkAndRegisterDirectories(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                registerDirectory(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
 
    /**
     * Process all events for keys queued to the watcher
     */
    void processEvents() {
        for (;;) {
 
            // wait for key to be signalled
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }
 
            Path dir = keys.get(key);
            if (dir == null) {
                System.err.println("WatchKey not recognized!!");
                continue;
            }
 
            for (WatchEvent<?> event : key.pollEvents()) {
                @SuppressWarnings("rawtypes")
                WatchEvent.Kind kind = event.kind();
 
                // Context for directory entry event is the file name of entry
                @SuppressWarnings("unchecked")
                Path name = ((WatchEvent<Path>)event).context();
                Path child = dir.resolve(name);
 
                // print out event
                System.out.format("%s: %s\n", event.kind().name(), child);
 
                // if directory is created, and watching recursively, then register it and its sub-directories
                if (kind == ENTRY_CREATE) {
                    try {
                        if (Files.isDirectory(child)) {
                            walkAndRegisterDirectories(child);
                        }
                    } catch (IOException x) {
                        // do something useful
                    }
                }
            }
 
            // reset key and remove from set if directory no longer accessible
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);
 
                // all directories are inaccessible
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }
 
    public static void main(String[] args) throws IOException {
        Path dir = Paths.get("c:/temp");
        new Java8WatchServiceExample(dir).processEvents();
    }
}

After running the program and adding or changing files or directories in the given input directory, you will notice the captured events in the console.

Output:

ENTRY_CREATE: c: \ temp \ New folder
ENTRY_DELETE: c: \ temp \ New folder
ENTRY_CREATE: c: \ temp \ data
ENTRY_CREATE: c: \ temp \ data \ New Text Document.txt
ENTRY_MODIFY: c: \ temp \ data
ENTRY_DELETE: c: \ temp \ data \ New Text Document.txt
ENTRY_CREATE: c: \ temp \ data \ tempFile.txt
ENTRY_MODIFY: c: \ temp \ data
ENTRY_MODIFY: c: \ temp \ data \ tempFile.txt
ENTRY_MODIFY: c: \ temp \ data \ tempFile.txt
ENTRY_MODIFY: c: \ temp \ data \ tempFile.txt

WatchService needs to keep two things in mind:

  1. WatchService does not pick up events for subdirectories of the monitored directory.
  2. We still need to poll WatchService events instead of receiving asynchronous notifications.
  3. WatchService can immediately monitor changes to files. If the event is created, deleted or updated, and the event is relative to the monitoring directory, watchevent The context method will return the Path object. It is important to know that when an event is received, the procedure for executing the operation cannot be guaranteed to have been completed, so a certain degree of coordination may be required, such as judging the success of the file operation.