深入 java 系列之 nio2 文件事件通知

Posted by NotGeek on December 27, 2018

深入 JAVA 系列之 NIO2 文件事件通知

本期议题

  • 传统文件事件通知
  • NIO2 文件事件通知
  • JVM 文件事件通知实现(Windows 为例)

  • NIO1 @since 1.4
  • NIO2 @since 1.7
  • AIO @since 1.7

传统文件事件通知

需求:

如何监听一个文件或者文件夹被修改了?

JVM 是以 C++ 为主,也有汇编代码,也有 c

1
2
3
4
5
6
7
private static void println() {
    File file = new File("");
    out.println("[用户工作目录]: " + file.getAbsolutePath());
    // 用户目录,用户工作目录
    out.println("[用户目录]:" + System.getProperty("user.home"));
    out.println("[用户工作目录]: " + System.getProperty("user.dir"));
}
1
2
3
[用户工作目录]: D:\GuPao_IDEA_xiaomage_workspace\GP-public\nio2-file-event
[用户目录]C:\Users\Darian
[用户工作目录]: D:\GuPao_IDEA_xiaomage_workspace\GP-public\nio2-file-event

java.io.File

1
2
3
4
5
6
7
8
9
private static final FileSystem fs = DefaultFileSystem.getFileSystem();

public File(String pathname) {
    if (pathname == null) {
        throw new NullPointerException();
    }
    this.path = fs.normalize(pathname);
    this.prefixLength = fs.prefixLength(this.path);
}

FileSystem 有多种实现

java.io.WinNTFileSystem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class WinNTFileSystem extends FileSystem {

    private final char slash;
    private final char altSlash;
    private final char semicolon;

    public WinNTFileSystem() {
        slash = AccessController.doPrivileged(
            new GetPropertyAction("file.separator")).charAt(0);
        semicolon = AccessController.doPrivileged(
            new GetPropertyAction("path.separator")).charAt(0);
        altSlash = (this.slash == '\\') ? '/' : '\\';
    }
    //。。。
}

我们现在的 Windows 操作系统都是在 NT 内核上写出来的。New Technology 新技术。

文件分隔符,都是取得 properties 这是操作系统,JVM 来设置的。

当你的 Tomcat 部署一个应用的时候,jar 包有两个,偶尔出现 类找不到的问题。

1
2
D:\anzhuanga\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\anzhuang\IntelliJ IDEA 2018.1.4\lib\idea_rt.jar=3568:D:\anzhuang\IntelliJ IDEA 2018.1.4\bin" -Dfile.encoding=UTF-8 -classpath D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\charsets.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\deploy.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\access-bridge-64.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\cldrdata.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\dnsns.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\jaccess.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\jfxrt.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\localedata.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\nashorn.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\sunec.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\sunjce_provider.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\sunmscapi.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\sunpkcs11.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\ext\zipfs.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\javaws.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\jce.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\jfr.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\jfxswt.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\jsse.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\management-agent.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\plugin.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\resources.jar;D:\anzhuanga\Java\jdk1.8.0_121\jre\lib\rt.jar;D:\GuPao_IDEA_xiaomage_workspace\GP-public\nio2-file-event\target\classes com.darian.io.file.monitor.FileMonitoring
D:\GuPao_IDEA_xiaomage_workspace\GP-public\nio2-file-event

运行日志 -classpath 这里的目录有一定的顺序,不同的操作系统的顺序不太一样。所以有的时候,有的类会找不到。

FileWatchDog

  • 单线程
  • 多线程

当你有层次性的时候,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class FileMonitoring {
    public static void main(String[] args) throws IOException, InterruptedException {
        File userDir = new File(System.getProperty("user.dir"));
        long lastModified = userDir.lastModified();
        List<String> subFiles = list(userDir.list());
        while (true) {
            // 文件最后的修改事件
            if (lastModified == userDir.lastModified()) {
                continue;
            }
            // 保存一份现有的文件
            List<String> newSubFiles = list(userDir.list());
            // 1. 增加文件
//            newSubFiles.removeAll(subFiles); // 剩余的文件

            List<String> finalSubFiles = subFiles;
            newSubFiles.stream()
                    .filter(s -> !finalSubFiles.contains(s))
                    .forEach(value -> out.println("新增的文件:" + value));

            finalSubFiles.stream()
                    .filter(s -> !newSubFiles.contains(s))
                    .forEach(value -> out.println("删除的文件:" + value));


            lastModified = userDir.lastModified();
            subFiles = list(userDir.list());
        }
    }

    private static <T> List<T> list(T... values) {
        return new ArrayList<>(Arrays.asList(values));
    }

    private static void println() {
        File file = new File("");
        out.println("[用户工作目录]: " + file.getAbsolutePath());
        // 用户目录,用户工作目录
        out.println("[用户目录]:" + System.getProperty("user.home"));
        out.println("[用户工作目录]: " + System.getProperty("user.dir"));
    }
}
1
2
新增的文件:aaa
删除的文件:aaa

mercyblitz : JDK 1.6 JDK 1.7

实现是否递归的调用。

可读可写的方法是否一样。更改以后,更改时间没有变

1
程序部署在对方的机器上。对方可能 JDK 6 ,也可能  JDK 1.7 ,也可能  JDK 1.5 ,根据对方 版本号,自动选用对用的版本的代码。

1545932473325

接口不变,实现的方式不同。

1
当 `pom.xml` 修改的时候,你会发现 maven 的依赖也修改了,其实我们 `.idea` 包里边也做了变化,idea 也做了监听。

文件事件类型

  • 创建
  • 修改
  • 删除
  • 异常

文件 API

  • File

NIO2 文件事件通知

文件事件类型

  • 创建:StandardWatchEventKinds.ENTRY_CREATE
  • 修改:StandardWatchEventKinds.ENTRY_MODIFY
  • 删除:StandardWatchEventKinds.ENTRY_DELETE
  • 异常:StandardWatchEventKinds.OVERFLOW

文件API

  • 服务
    • WatchService
  • 事件
    • WatchEvent

java.nio.channels.SelectableChannel

1
2
public abstract SelectionKey register(Selector sel, int ops, Object att)
    throws ClosedChannelException;

和 NIO 里边的 channel 差不多。

JVM 文件事件通知实现(Windows 为例)

理解 Windows ReadDirectoryChangW 函数

JVM 源码下载

https://download.java.net/openjdk/jdk8

附加未知 JDK 源码

NIO2 文件事件实现 JVM 源码