NIO--WatchService、WatchKey(监控文件变化)

转载自:http://blog.csdn.net/lirx_tech/article/details/51425364


1. 旧版本监控文件变化的弊端:

    1) 非常繁琐,必须自己手动开启一个后台线程每隔一段时间遍历一次目标节点并记录当前状态,然后和上一次遍历的状态对比,如果不相同就表示发生了变化,再采取相应的操作,这个过程非常长,都需要用户自己手动实现;

    2) 效率低:效率都消耗在了遍历、保存状态、对比状态上了!这是因为旧版本的Java无法很好的利用OS文件系统的功能,因此只能这样笨拙地监控文件变化;

    3) 无法利用OS的很多功能:上一点已经阐明;


2. WatchService:

    1) 该类的对象就是操作系统原生的文件系统监控器!我们都知道OS自己的文件系统监控器可以监控系统上所有文件的变化,这种监控是无需遍历、无需比较的,是一种基于信号收发的监控,因此效率一定是最高的;现在Java对其进行了包装,可以直接在Java程序中使用OS的文件系统监控器了;

    2) 获取当前OS平台下的文件系统监控器:

         i. WatchService watcher = FileSystems.getDefault().newWatchService();

         ii. 从FileSystems这个类名就可以看出这肯定是属于OS平台文件系统的,接下来可以看出这一连串方法直接可以得到一个文件监控器;

!!这里暂时不用深入理解这串方法的具体含义,先知道怎么用就行了;

    3) 我们都知道,操作系统上可以同时开启多个监控器,因此在Java程序中也不例外,上面的代码只是获得了一个监控器,你还可以用同样的代码同时获得多个监控器;

    4) 监控器其实就是一个后台线程,在后台监控文件变化所发出的信号,这里通过上述代码获得的监控器还只是一个刚刚初始化的线程,连就绪状态都没有进入,只是初始化而已;


3. 将监控器注册给指定的文件节点——开启监控线程来监控指定的节点:

    1) 首先监控需要有个目标吧,你需要指定监控哪个文件节点;

    2) 其次就是文件变化的种类有很多,有创建、删除、修改等多种类型的文件变化;

    3) Path对象的register方法搞定一切:

         i. 原型:WatchKey Path.register(WatchService watcher, WatchEvent.Kind<?>... events);

         ii. 其意义很明确,就是将指定的监控器注册给Path对象所代表的文件节点,而events则指定了监控器监控哪些类型的文件变化;

         iii. 该方法同时也会让监控器线程就绪并运行,该方法调用完后监控器就彻底开始监控了!!

    4) WatchEvent.Kind<?> events:

         i. 一看到Kind就知道该类一定是一个枚举类,里面定义了文件变化的各种枚举类型;

         ii. 这些枚举变量都定义在StandardWatchEventKinds中,主要有三个:

ENTRY_CREATE:创建

ENTRY_DELETE:删除

ENTRY_MODIFY:修改

    5) WatchKey:

         i. 即监控键,说的明确一点就是该文件节点所绑定的监控器的监控信息池,即文件节点的监控池,简称监控池;

         ii. 所有监控到的信息都会放到监控池中;

         iii. register方法返回的就是节点的监控池;

    6) 监控池是静态的!!!

         i. 监控池只能表示某一个时间节点下的文件变化信息,并不能动态保存这些信息;像register方法,刚注册完是返回的监控池是一个空的监控池,因为刚刚开启线程,什么都还没有发生,即使后面发生了文件修改,那么该监控池对象的内容还是保持不变,仍然是空的;

!!Java要求,只有当你主动去获取新的监控池时才会将更新的内容放入获取到的监控池中!

!!这是因为应用程序往往有这样的需求,就是当文件发生变化的这个时间节点上进行一定的处理,那何不就刚好在这个时间点上去更新监控池呢?

!!当然Java也完全可以做到实时更新监控池,这样的话register返回的那个监控池就会实时变化,可以一直用到底,但是你要想,WatchService其实是操作系统的线程,而监控池是Java程序内的内存空间,要将文件变化实时反映出来那就需要Java进程和操作系统的监控进程实时交流才行,这样岂不是在进程间通信的时间会花费很多,那还是干脆只有在文件发生变化的时间点上程序通过主动获取监控池来将监控信息传入Java程序来的省时省力;

         ii. 获取下一个监控信息:其实就是获取新的监控池

             a. WatchKey WatchService.poll(); // 尝试获取下一个变化信息的监控池,如果没有变化则返回null

             b. WatchKey WatchService.take(); // // 尝试获取下一个变化信息的监控池,如果没有变化则一直等待

!!这两个方法都是WatchService的对象方法,从这点可以看出,获取更新的监控信息其实需要当前Java进程和操作系统进程之间进行交流,返回的WatchKey是当前Java程序的(内存空间位于当前Java程序中);

         iii. 如果需要长时间一直监控要用take,而如果只是在某个指定的时间监控则用poll;


4. 获得WatchKey(监控池)中的具体监控信息:

    1) 一个文件变化动作可能会引发一系列的事件,因此WatchKey中保存着一个事件列表List<WatchEvent<?>> list,可以通过WatchKey的pollEvents方法获得该列表:

        i. 原型:List<WatchEvent<?>> WatchKey.pollEvents();

        ii. 获得列表后既可以通过for循环迭代遍历该变化动作所引发的所有具体事件(通常来说,大多数变化动作只会引发一个事件);

    2) WatchEvent<?>:

        i. 表示监控时间对象,里面最常用的两个方法就是context和kind;

        ii. Path WatchEvent<?>.context(); // 返回触发该事件的那个文件或目录的路径

        iii. Kind<StandardWatchEventKinds> WatchEvent<?>kind(); // 返回事件类型(ENTRY_CREATE、ENTRY_DELETE、ENTRY_MODIFY之一);


5. 完成一次监控就需要重置监控器一次:

    1) 直接调用WatchKey的reset方法就代表重置其关联的监控器了;

    2) 原型:boolean WatchKey.reset();

    3) 因为当你使用poll或take时监控器线程就被阻塞了,因为你处理文件变化的操作可能需要挺长时间的,为了防止在这段时间内又要处理其他类似的事件,因此需要阻塞监控器线程,而调用reset表示重启该线程;


6. 示例:

[java]  view plain  copy
  1. public class Test {  
  2.       
  3.     public static void main(String[] args) throws IOException, InterruptedException {  
  4.         WatchService watcher = FileSystems.getDefault().newWatchService();  
  5.         Paths.get("C:").register(watcher,   
  6.                 StandardWatchEventKinds.ENTRY_CREATE,  
  7.                 StandardWatchEventKinds.ENTRY_DELETE,  
  8.                 StandardWatchEventKinds.ENTRY_MODIFY);  
  9.           
  10.         while (true) {  
  11.             WatchKey key = watcher.take();  
  12.             for (WatchEvent<?> event: key.pollEvents()) {  
  13.                 System.out.println(event.context() + " comes to " + event.kind());  
  14.             }  
  15.               
  16.             boolean valid = key.reset();  
  17.             if (!valid) {  
  18.                 break;  
  19.             }  
  20.         }  
  21.     }  
  22. }  


7. 监控范围详解:

    1) 例如以下文件结构:

A(dir)  --- B(file)

           --- C(dir)  --- D(file)

                            --- E(dir)   ---  F...

A目录中有两个子文件,其中B是文件C是子目录,C下又有两个子文件,D是文件,E是子目录,而E目录下还有其它东西F等等;

    2) 现在监控A结点,那么其监控范围就只有A所有子节点,但不包括A子节点的子节点!

    3) 例如:你修改B,或者在A的目录下创建/删除文件或者目录都会被监控到;

    4) 当然修改C也会被监控到,那C是目录,目录怎么修改呢?在操作系统中目录的修改是这样定义的,你在该目录下新建、删除、重命名其直接子节点都算对该结点进行修改;

!!这其实很好理解,以为目录其实在操作系统底层是一个简单的文本文件(你可以用Vim等编辑器打开查看),里面记录了该目录下的所有子文件(文件/目录)的名称,设想,你对一个文件进行修改其实就是更改文件中的内容,那么:

        a. 新建子节点:就是在该文本文件中增加一条记录,那当然算对该目录进行修改咯!

        b. 删除子节点:在该文本文件中删除一条记录;

        c. 重命名:改写其中一条记录;

!!因此这些都是赤裸裸地对目录进行修改啊!!

    5) 小结:监控范围就是该节点下所有子节点(文件、目录(目录就是一个文本文件))的所有创建、删除、修改行为;


猜你喜欢

转载自blog.csdn.net/szzt_lingpeng/article/details/78973148