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

Java函数式编程(十二):监控文件修改

程序员文章站 2024-02-28 23:15:52
使用flatmap列出子目录 前面已经看到如何列出指定目录下的文件了。我们再来看下如何遍历指定目录的直接子目录(深度为1),先实现一个简单的版本,然后再用更方便的flat...

使用flatmap列出子目录

前面已经看到如何列出指定目录下的文件了。我们再来看下如何遍历指定目录的直接子目录(深度为1),先实现一个简单的版本,然后再用更方便的flatmap()方法来实现。

我们先用传统的for循环来遍历一个指定的目录。如果子目录中有文件,就添加到列表里;否则就把子目录添加到列表里。最后,打印出所有文件的总数。代码在下面——这个是困难模式的。

复制代码 代码如下:

public static void listthehardway() {
     list<file> files = new arraylist<>();
     file[] filesincurrentdir = new file(".").listfiles();
     for(file file : 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()方法的使用。

复制代码 代码如下:

public static void betterway() {
     list<file> files =
          stream.of(new file(".").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()方法来将直接子目录中的所有文件列出来。下面我们来监控一下文件的修改操作。

监控文件修改

我们已经知道如何查找文件及目录,不过如果我们希望在文件创建,修改或删除的时候,能够接收到提示消息的话,这个也非常简单。这样的机制对于监视一些特殊文件比如配置文件,系统资源的改动非常有用。下面我们来探索下java 7中引入的这个工具,watchservice,它可以用来监控文件的修改。下面我们看到的许多特性都来自jdk 7,而这里最大的改进就是内部迭代器带来的便利性。

我们先来写个监控当前目录中的文件修改的例子。jdk中的path类会对应文件系统中的一个实例,它是一个观察者服务的工厂。我们可以给这个服务注册通知事件,就像这样:

复制代码 代码如下:

inal path path = paths.get(".");

final watchservice watchservice =
       path.getfilesystem()
           .newwatchservice();
       path.register(watchservice, standardwatcheventkinds.entry_modify);

system.out.println("report any file changed within next 1 minute...");

我们注册了一个watchservice来观察当前目录的修改。你可以轮询这个watchservice来获取目录下文件的修改操作,它会通过一个watchkey将这些改动返回给我们。一旦我们拿到了这个key,可以遍历它的所有事件来获取文件更新的详细信息。因为可能会有多个文件被同时修改,poll操作可能会返回多个事件。来看下轮询以及遍历的代码。

复制代码 代码如下:

final watchkey watchkey = watchservice.poll(1, timeunit.minutes);

if(watchkey != null) {
     watchkey.pollevents()
          .stream()
          .foreach(event ->
               system.out.println(event.context()));
}

这里可以看到,java 7和java 8的特性同时出场了。我们把pollevents()返回的集合转化成了一个java 8的stream,然后使用它的内部迭代器来打印出每个文件的详细的更新信息。

我们来运行下这段代码,然后将当前目录下的sample.txt文件修改一下,看下这个程序是否能察觉这个更新。

复制代码 代码如下:

report any file changed within next 1 minute...

sample.txt

当我们修改了这个文件的时候,程序会提示说文件被修改了。我们可以用这个功能来监视不同文件的更新,然后执行相应的任务。当然我们也可以只注册文件新建或者删除的操作。

总结

有了lambda表达式和方法引用后,像字符串及文件的操作,创建自定义比较器这些常见的任务都变得更简单也更简洁了。匿名内部类也变得优雅起来了,而可变性就像日出后的晨雾一样,也消失得无影无踪了。使用这种新风格进行编码还有一个福利,就是你可以使用jdk的新设施来高效地遍历庞大的目录。

现在你已经知道如何创建lambda表达式并把它传递给方法了。下一章我们会介绍如何使用函数式接口及lambda表达式进行软件的设计。