Java函数式编程(十二):监控文件修改
使用flatMap列出子目录
前面已经看到如何列出指定目录下的文件了。我们再来看下如何遍历指定目录的直接子目录(深度为1),先实现一个简单的版本,然后再用更方便的flatMap()方法来实现。
我们先用传统的for循环来遍历一个指定的目录。如果子目录中有文件,就添加到列表里;否则就把子目录添加到列表里。最后,打印出所有文件的总数。代码在下面——这个是困难模式的。
publicstaticvoidlistTheHardWay(){ List<File>files=newArrayList<>(); File[]filesInCurrentDir=newFile(".").listFiles(); for(Filefile:filesInCurrentDir){ File[]filesInSubDir=file.listFiles(); if(filesInSubDir!=null){ files.addAll(Arrays.asList(filesInSubDir)); }else{ files.add(file); } } System.out.println("Count:"+files.size()) }
我们先获取当前目录下的文件列表,然后进行遍历。对于每个文件,如果它有子文件,就把它们添加到列表中。这样做是没问题的,不过它有一些常见的问题:可变性,基本类型偏执,命令式,代码冗长,等等。一个叫flatMap()的小方法就可以解决掉这些问题。
正如这个名字所说的,这个方法在映射后会进行扁平化。它会像map()一样对集合中的元素进行映射。但是和map()方法不同的是,map()方法里面的lambda表达式只是返回一个元素,而这里返回的是一个Stream对象。于是这个方法将多个流压平,将里面的每个元素映射到一个扁平化的流中。
我们可以用flatMap()来执行各种操作,不过现在手头的这个问题就正好诠释了它的价值。每个子目录都有一个文件的列表或者说流,而我们希望获取当前目录下的所有子目录中的文件列表。
有一些目录可能是空的,或者说没有子元素。这种情况下,我们将这个空目录或者文件包装成一个流对象。如果我们想忽略某个文件,JDK中的flatMap()方法也可以很好的处理空文件;它会把一个空引用作为一个空集合合并到流里。来看下flatMap()方法的使用。
publicstaticvoidbetterWay(){ List<File>files= Stream.of(newFile(".").listFiles()) .flatMap(file->file.listFiles()==null? Stream.of(file):Stream.of(file.listFiles())) .collect(toList()); System.out.println("Count:"+files.size()); }
我们先是获取了当前目录的子文件流,然后调用了它的flatMap()方法。然后将一个lambda表达式传给这个方法,这个表达式会返回指定文件的子文件的流。flatMap()方法返回的的是当前目录所有子目录下的文件的集合。我们使用collect()方法以及Collectors里面的toList()(方法把它们收集到一个列表中。
我们传给flatMap()的这个lambda表达式,它返回的是一个文件的子文件。如果没有的话,则返回这个文件的流。flatMap()方法优雅地将这个流映射到一个流的集合中,然后将这个集合扁平化,最终合并到一个流中。
flatMap()方法减少了许多开发的工作——它将两个连续的操作很好的结合到了一起,这通常称为元组——用一个优雅的操作就完成了。
我们已经知道如何使用flatMap()方法来将直接子目录中的所有文件列出来。下面我们来监控一下文件的修改操作。
监控文件修改
我们已经知道如何查找文件及目录,不过如果我们希望在文件创建,修改或删除的时候,能够接收到提示消息的话,这个也非常简单。这样的机制对于监视一些特殊文件比如配置文件,系统资源的改动非常有用。下面我们来探索下Java7中引入的这个工具,WatchService,它可以用来监控文件的修改。下面我们看到的许多特性都来自JDK7,而这里最大的改进就是内部迭代器带来的便利性。
我们先来写个监控当前目录中的文件修改的例子。JDK中的Path类会对应文件系统中的一个实例,它是一个观察者服务的工厂。我们可以给这个服务注册通知事件,就像这样:
inalPathpath=Paths.get(".");
finalWatchServicewatchService= path.getFileSystem() .newWatchService(); path.register(watchService,StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Reportanyfilechangedwithinnext1minute...");