欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[NIO.2] 第四十篇 监控目录树

程序员文章站 2022-04-25 10:04:02
...
现在,我们编写一个应用来监控 C:\rafaelnadal 目录。此外,如果在这个目录中发生了 CREATE 事件创建了一个新目录,那么这个目录将会被立即注册,和 C:\rafaelnadal 中一开始就存在的目录一样。

首先,创建监控服务:

private WatchService watchService = FileSystems.getDefault().newWatchService();

然后,需要注册目录树的创建、删除和修改事件。稍微麻烦的一点是我们需要注册  C:\rafaelnadal 的所有子目录,而不仅仅只是一个目录。因此需要递归遍历所有子目录,并将其独立注册到监控服务中。要完成这个任务可以使用 SimpleFileVisitor 类,并覆盖 preVisitDirectory 方法,如果你需要处理一些未知的遍历异常,还可以覆盖 visitFileFailed() 方法。接下来,我们按照前面这些描述编写一个方法,名为 registerTree():

private void registerTree(Path start) throws IOException { 
 
  Files.walkFileTree(start, new SimpleFileVisitor<Path>() { 
 
     @Override 
     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
                                                                          throws IOException { 
        System.out.println("Registering:" + dir); 
        registerPath(dir); 
        return FileVisitResult.CONTINUE; 
        } 
   }); 
}


如你所见,上面的方法将注册的代码单独放到了 registerPath() 方法中:

private void registerPath(Path path) throws IOException { 
 
  //register the received path 
  WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,  
                  StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
}


现在,所有  C:\rafaelnadal 树下的子目录都注册了新增,修改和删除事件。

接下来,我们需要使用无限循环捕获这些事件。当事件发生后,我们更关注是否是新增事件,如果是新增子目录,那么新增的子目录也需要立即调用  registerTree() 注册到监控服务中。现在有个问题是我们不知道 WatchKey 对应的 Path,因此没办法调用 registerTree() 进行注册。解决办法是将 WatchKey 和对应的 Path 存放到 HashMap 中,在 registerPath() 方法中更新 HashMap 的内容。这样,如果事件发生后,我们就可以直接从 HashMap 中获取 Path 对象:

private final Map<WatchKey, Path> directories = new HashMap<>(); 
… 
private void registerPath(Path path) throws IOException { 
  //register the received path 
  WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,   
                  StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); 
 
  //store the key and path 
  directories.put(key, path); 
}


现在,在无限循环中可以从 HashMap 获取 Path:

… 
while (true) { 
  … 
  if (kind == StandardWatchEventKinds.ENTRY_CREATE) { 
      final Path directory_path = directories.get(key); 
      final Path child = directory_path.resolve(filename); 
 
      if (Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) { 
        registerTree(child); 
      } 
  } 
… 
} 
…


HashMap 还可以作为结束无线循环的条件,当 WatchKey 无效时,从 HashMap 中移除,并在 HashMap 为空时跳出循环:

… 
while (true) { 
  … 
  //reset the key 
  boolean valid = key.reset(); 
 
  //remove the key if it is not valid 
  if (!valid) { 
      directories.remove(key); 
 
      if (directories.isEmpty()) { 
         break; 
      } 
  } 
} 
…


综上所述,编写一个完整的应用:

import java.io.IOException; 
import java.nio.file.FileSystems; 
import java.nio.file.FileVisitResult; 
import java.nio.file.Files; 
import java.nio.file.LinkOption; 
import java.nio.file.Path; 
import java.nio.file.Paths; 
import java.nio.file.SimpleFileVisitor; 
import java.nio.file.StandardWatchEventKinds; 
import java.nio.file.WatchEvent; 
import java.nio.file.WatchEvent.Kind; 
import java.nio.file.WatchKey; 
import java.nio.file.WatchService; 
import java.nio.file.attribute.BasicFileAttributes; 
import java.util.HashMap; 
import java.util.Map; 
 
class WatchRecursiveRafaelNadal { 
 
    private WatchService watchService; 
    private final Map<WatchKey, Path> directories = new HashMap<>(); 
 
    private void registerPath(Path path) throws IOException { 
        //register the received path 
        WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,  
                  StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); 
 
        //store the key and path 
        directories.put(key, path); 
    } 
    private void registerTree(Path start) throws IOException { 
 
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() { 
 
            @Override 
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
                    throws IOException { 
                System.out.println("Registering:" + dir); 
                registerPath(dir); 
                return FileVisitResult.CONTINUE; 
            } 
        }); 
 
    } 
 
    public void watchRNDir(Path start) throws IOException, InterruptedException { 
 
        watchService = FileSystems.getDefault().newWatchService(); 
 
        registerTree(start); 
 
        //start an infinite loop 
        while (true) { 
 
            //retrieve and remove the next watch key 
            final WatchKey key = watchService.take(); 
 
            //get list of events for the watch key 
            for (WatchEvent<?> watchEvent : key.pollEvents()) { 
 
                //get the kind of event (create, modify, delete) 
                final Kind<?> kind = watchEvent.kind(); 
 
                //get the filename for the event 
                final WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent; 
                final Path filename = watchEventPath.context(); 
 
                //handle OVERFLOW event 
                if (kind == StandardWatchEventKinds.OVERFLOW) { 
                    continue; 
                } 
 
                //handle CREATE event 
                if (kind == StandardWatchEventKinds.ENTRY_CREATE) { 
                    final Path directory_path = directories.get(key); 
                    final Path child = directory_path.resolve(filename); 
 
                    if (Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) { 
                        registerTree(child); 
                    } 
                } 
                //print it out 
                System.out.println(kind + " -> " + filename); 
            } 
 
            //reset the key 
            boolean valid = key.reset(); 
 
            //remove the key if it is not valid 
            if (!valid) { 
                directories.remove(key); 
 
                //there are no more keys registered 
                if (directories.isEmpty()) { 
                    break; 
                } 
            } 
        } 
        watchService.close(); 
    } 
} 
 
public class Main { 
 
    public static void main(String[] args) { 
 
        final Path path = Paths.get("C:/rafaelnadal"); 
        WatchRecursiveRafaelNadal watch = new WatchRecursiveRafaelNadal(); 
 
        try { 
            watch.watchRNDir(path); 
        } catch (IOException | InterruptedException ex) { 
            System.err.println(ex); 
        } 
 
    } 
} 


测试的时候,可以在子目录中创建目录或文件,删除移动目录或文件,观察输出结果。

文章来源:http://www.aptusource.org/2014/04/nio-2-watching-a-directory-tree/
相关标签: Java NIO.2