Java 7 watchservice获取文件更改偏移量 [英] Java 7 watchservice get file change offset

查看:280
本文介绍了Java 7 watchservice获取文件更改偏移量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚使用Java 7 WatchService来监控文件以进行更改。



以下是我敲了一些代码:

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

路径路径= Paths.get(c:\\ testing);

path.register(watcher,StandardWatchEventKinds.ENTRY_MODIFY);

while(true){
WatchKey key = watcher.take();

for(WatchEvent event:key.pollEvents()){
System.out.println(event.kind()+:+ event.context());
}

boolean valid = key.reset();
if(!valid){
break;
}
}

这似乎有效,我收到通知当文件'changethis.txt'被修改时。



但是,除了能够在文件发生变化时通知,无论如何都要通知修改发生在文件中的位置?



我查看了Java文档,但我似乎无法找到任何内容。



这可能是使用WatchService,还是必须自定义?



谢谢

解决方案

为了它的价值,我已经破解了一个小概念验证,它可以



$ ul>
  • 在监视目录中检测添加,修改和删除的文件,

  • 显示每次更改的统一差异(添加/删除文件时也是完全差异),

  • 通过保持源可怕的阴影副本来跟踪连续的更改ctory,

  • 以用户定义的节奏工作(默认为5秒),以便在短时间内不会打印太多小差异,而是在一段时间内打印出更大的差异虽然。



  • 生产环境存在一些限制因素:




    • 为了不使示例代码复杂化,必须在创建影子目录时复制子目录(因为我已经回收了现有方法来创建深层目录副本),但是在运行时忽略。只监视被监视目录下面的文件,以避免递归。

    • 不满足您不使用外部库的要求,因为我真的想避免重新发明轮子以进行统一diff创建。

    • 这个解决方案的最大优势 - 它能够检测文本文件中任何地方的变化,而不仅仅是在文件的末尾,如 tail -f - 也是它的最大缺点:每当文件发生变化时,必须完全复制阴影,否则程序无法检测到后续更改。所以我不推荐这个解决方案用于非常大的文件。



    如何构建:

     <?xml version =1.0encoding =UTF-8?> 
    < project xmlns =http://maven.apache.org/POM/4.0.0xmlns:xsi =http://www.w3.org/2001/XMLSchema-instancexsi:schemaLocation =http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">
    < modelVersion> 4.0.0< / modelVersion>

    < groupId> de.scrum-master.tools< / groupId>
    < artifactId> SO_WatchServiceChangeLocationInFile< / artifactId>
    < version> 1.0-SNAPSHOT< / version>

    < properties>
    < project.build.sourceEncoding> UTF-8< /project.build.sourceEncoding>
    < / properties>

    < build>
    < plugins>
    < plugin>
    < artifactId> maven-compiler-plugin< / artifactId>
    < version> 3.1< / version>
    < configuration>
    < source> 1.7< / source>
    < target> 1.7< / target>
    < / configuration>
    < / plugin>
    < / plugins>
    < / build>

    < dependencies>
    < dependency>
    < groupId> com.googlecode.java-diff-utils< / groupId>
    < artifactId> diffutils< / artifactId>
    < version> 1.3.0< / version>
    < / dependency>
    < / dependencies>
    < / project>

    源代码(对不起,有点冗长):

      package de.scrum_master.app; 

    import difflib.DiffUtils;

    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    import java.nio.file。*;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.util.LinkedList;
    import java.util.List;

    import static java.nio.file.StandardWatchEventKinds。*;

    公共类FileChangeWatcher {
    public static final String DEFAULT_WATCH_DIR =watch-dir;
    public static final String DEFAULT_SHADOW_DIR =shadow-dir;
    public static final int DEFAULT_WATCH_INTERVAL = 5;

    private Path watchDir;
    private Path shadowDir;
    private int watchInterval;
    private WatchService watchService;

    public FileChangeWatcher(Path watchDir,Path shadowDir,int watchInterval)抛出IOException {
    this.watchDir = watchDir;
    this.shadowDir = shadowDir;
    this.watchInterval = watchInterval;
    watchService = FileSystems.getDefault()。newWatchService();
    }

    public void run()抛出InterruptedException,IOException {
    prepareShadowDir();
    watchDir.register(watchService,ENTRY_CREATE,ENTRY_MODIFY,ENTRY_DELETE);
    while(true){
    WatchKey watchKey = watchService.take();
    for(WatchEvent<?> event:watchKey.pollEvents()){
    Path oldFile = shadowDir.resolve((Path)event.context());
    Path newFile = watchDir.resolve((Path)event.context());
    List< String> oldContent;
    List< String> newContent;
    WatchEvent.Kind<?> eventType = event.kind();
    if(!(Files.isDirectory(newFile)|| Files.isDirectory(oldFile))){
    if(eventType == ENTRY_CREATE){
    if(!Files.isDirectory(newFile) )
    Files.createFile(oldFile);
    } else if(eventType == ENTRY_MODIFY){
    Thread.sleep(200);
    oldContent = fileToLines(oldFile);
    newContent = fileToLines(newFile);
    printUnifiedDiff(newFile,oldFile,oldContent,newContent);
    try {
    Files.copy(newFile,oldFile,StandardCopyOption.REPLACE_EXISTING);
    } catch(例外e){
    e.printStackTrace();
    }
    }否则if(eventType == ENTRY_DELETE){
    try {
    oldContent = fileToLines(oldFile);
    newContent = new LinkedList<>();
    printUnifiedDiff(newFile,oldFile,oldContent,newContent);
    Files.deleteIfExists(oldFile);
    } catch(例外e){
    e.printStackTrace();
    }
    }
    }
    }
    watchKey.reset();
    Thread.sleep(1000 * watchInterval);
    }
    }

    private void prepareShadowDir()抛出IOException {
    recursiveDeleteDir(shadowDir);
    Runtime.getRuntime()。addShutdownHook(
    new Thread(){
    @Override
    public void run(){
    try {
    System.out .println(清理影子目录+ shadowDir);
    recursiveDeleteDir(shadowDir);
    } catch(IOException e){
    e.printStackTrace();
    }
    }
    }
    );
    recursiveCopyDir(watchDir,shadowDir);
    }

    public static void recursiveDeleteDir(Path directory)throws IOException {
    if(!directory.toFile()。exists())
    return;
    Files.walkFileTree(目录,新的SimpleFileVisitor<路径>(){
    @Override
    public FileVisitResult visitFile(Path file,BasicFileAttributes attrs)抛出IOException {
    Files.delete(file );
    返回FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir,IOException exc)抛出IOException {
    Files.delete (dir);
    返回FileVisitResult.CONTINUE;
    }
    });
    }

    public static void recursiveCopyDir(final Path sourceDir,final Path targetDir)抛出IOException {
    Files.walkFileTree(sourceDir,new SimpleFileVisitor< Path>(){
    @Override
    public FileVisitResult visitFile(Path file,BasicFileAttributes attrs)抛出IOException {
    Files.copy(file,Paths.get(file.toString()。replace(sourceDir.toString(),targetDir。 toString())));
    返回FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir,BasicFileAttributes attrs)抛出IOException {
    Files.createDirectories(Paths.get(dir.toString()。replace(sourceDir.toString(),targetDir.toString())));
    返回FileVisitResult.CONTINUE;
    }
    });
    }

    private static List< String> fileToLines(Path path)抛出IOException {
    List< String> lines = new LinkedList<>();
    字符串行;
    try(BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))){
    while((line = reader.readLine())!= null)
    lines.add (线);
    }
    catch(例外e){}
    返回行;
    }

    private static void printUnifiedDiff(Path oldPath,Path newPath,List< String> oldContent,List< String> newContent){
    List< String> diffLines = DiffUtils.generateUnifiedDiff(
    newPath.toString(),
    oldPath.toString(),
    oldContent,
    DiffUtils.diff(oldContent,newContent),
    3
    );
    System.out.println();
    for(String diffLine:diffLines)
    System.out.println(diffLine);
    }

    public static void main(String [] args)抛出IOException,InterruptedException {
    String watchDirName = args.length> 0? args [0]:DEFAULT_WATCH_DIR;
    String shadowDirName = args.length> 1? args [1]:DEFAULT_SHADOW_DIR;
    int watchInterval = args.length> 2? Integer.getInteger(args [2]):DEFAULT_WATCH_INTERVAL;
    new FileChangeWatcher(Paths.get(watchDirName),Paths.get(shadowDirName),watchInterval).run();
    }
    }

    我建议使用默认设置(例如使用源目录名为watch-dir)并使用它一段时间,在编辑器中创建和编辑一些文本文件时观察控制台输出。它有助于理解软件的内部机制。如果出现问题,例如在一个5秒的节奏内创建一个文件但又快速删除,没有任何复制或差异,所以程序只会打印一个堆栈跟踪到 System.err


    I've just been playing around with the Java 7 WatchService for monitoring a file for change.

    Here's a little bit of code I knocked up:

    WatchService watcher = FileSystems.getDefault().newWatchService();
    
        Path path = Paths.get("c:\\testing");
    
        path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
    
        while (true) {
            WatchKey key = watcher.take();
    
            for (WatchEvent event : key.pollEvents()) {
                System.out.println(event.kind() + ":" + event.context());
            }
    
            boolean valid = key.reset();
            if (!valid) {
                break;
            }
        }
    

    This seems to be working, and I get notifications as to when a file 'changethis.txt' gets modified.

    However, in addition to being able to notify when a file changes, is there anyway of being notified as to the location within the file that the modification occurred?

    I've had a look through the Java docs but I can't seem to find anything.

    Is this possible using the WatchService, or would something custom have to be implemented?

    Thanks

    解决方案

    For what it is worth, I have hacked a little proof of concept which is able to

    • detect added, modified and deleted files in a watched directory,
    • displaying unified diffs for each change (also full diffs when files were added/deleted),
    • keeping track of successive changes by keeping a shadow copy of the source directory,
    • work in a user-defined rhythm (default is 5 seconds) so as not to print too many small diffs in a short period of time, but rather somewhat bigger ones once in a while.

    There are several limitations which would be impediments in production environments:

    • In order to not complicate the sample code more than necessary, subdirectories are copied at the beginning when the shadow directory is created (because I have recycled an existing method to create a deep directory copy), but ignored during runtime. Only files right below the watched directory are being monitored so as to avoid recursion.
    • Your requirement not to use external libraries is not met because I really wanted to avoid re-inventing the wheel for unified diff creation.
    • This solution's biggest advantage - it is able to detect changes anywhere in a text file, not only at the end of file like tail -f - is also its biggest disadvantage: Whenever a file changes it must be fully shadow-copied because otherwise the program cannot detect the subsequent change. So I would not recommend this solution for very big files.

    How to build:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>de.scrum-master.tools</groupId>
        <artifactId>SO_WatchServiceChangeLocationInFile</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>com.googlecode.java-diff-utils</groupId>
                <artifactId>diffutils</artifactId>
                <version>1.3.0</version>
            </dependency>
        </dependencies>
    </project>
    

    Source code (sorry, a bit lengthy):

    package de.scrum_master.app;
    
    import difflib.DiffUtils;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.util.LinkedList;
    import java.util.List;
    
    import static java.nio.file.StandardWatchEventKinds.*;
    
    public class FileChangeWatcher {
        public static final String DEFAULT_WATCH_DIR = "watch-dir";
        public static final String DEFAULT_SHADOW_DIR = "shadow-dir";
        public static final int DEFAULT_WATCH_INTERVAL = 5;
    
        private Path watchDir;
        private Path shadowDir;
        private int watchInterval;
        private WatchService watchService;
    
        public FileChangeWatcher(Path watchDir, Path shadowDir, int watchInterval) throws IOException {
            this.watchDir = watchDir;
            this.shadowDir = shadowDir;
            this.watchInterval = watchInterval;
            watchService = FileSystems.getDefault().newWatchService();
        }
    
        public void run() throws InterruptedException, IOException {
            prepareShadowDir();
            watchDir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
            while (true) {
                WatchKey watchKey = watchService.take();
                for (WatchEvent<?> event : watchKey.pollEvents()) {
                    Path oldFile = shadowDir.resolve((Path) event.context());
                    Path newFile = watchDir.resolve((Path) event.context());
                    List<String> oldContent;
                    List<String> newContent;
                    WatchEvent.Kind<?> eventType = event.kind();
                    if (!(Files.isDirectory(newFile) || Files.isDirectory(oldFile))) {
                        if (eventType == ENTRY_CREATE) {
                            if (!Files.isDirectory(newFile))
                                Files.createFile(oldFile);
                        } else if (eventType == ENTRY_MODIFY) {
                            Thread.sleep(200);
                            oldContent = fileToLines(oldFile);
                            newContent = fileToLines(newFile);
                            printUnifiedDiff(newFile, oldFile, oldContent, newContent);
                            try {
                                Files.copy(newFile, oldFile, StandardCopyOption.REPLACE_EXISTING);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        } else if (eventType == ENTRY_DELETE) {
                            try {
                                oldContent = fileToLines(oldFile);
                                newContent = new LinkedList<>();
                                printUnifiedDiff(newFile, oldFile, oldContent, newContent);
                                Files.deleteIfExists(oldFile);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                watchKey.reset();
                Thread.sleep(1000 * watchInterval);
            }
        }
    
        private void prepareShadowDir() throws IOException {
            recursiveDeleteDir(shadowDir);
            Runtime.getRuntime().addShutdownHook(
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            System.out.println("Cleaning up shadow directory " + shadowDir);
                            recursiveDeleteDir(shadowDir);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            );
            recursiveCopyDir(watchDir, shadowDir);
        }
    
        public static void recursiveDeleteDir(Path directory) throws IOException {
            if (!directory.toFile().exists())
                return;
            Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }
    
                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    
        public static void recursiveCopyDir(final Path sourceDir, final Path targetDir) throws IOException {
            Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.copy(file, Paths.get(file.toString().replace(sourceDir.toString(), targetDir.toString())));
                    return FileVisitResult.CONTINUE;
                }
    
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    Files.createDirectories(Paths.get(dir.toString().replace(sourceDir.toString(), targetDir.toString())));
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    
        private static List<String> fileToLines(Path path) throws IOException {
            List<String> lines = new LinkedList<>();
            String line;
            try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) {
                while ((line = reader.readLine()) != null)
                    lines.add(line);
            }
            catch (Exception e) {}
            return lines;
        }
    
        private static void printUnifiedDiff(Path oldPath, Path newPath, List<String> oldContent, List<String> newContent) {
            List<String> diffLines = DiffUtils.generateUnifiedDiff(
                newPath.toString(),
                oldPath.toString(),
                oldContent,
                DiffUtils.diff(oldContent, newContent),
                3
            );
            System.out.println();
            for (String diffLine : diffLines)
                System.out.println(diffLine);
        }
    
        public static void main(String[] args) throws IOException, InterruptedException {
            String watchDirName = args.length > 0 ? args[0] : DEFAULT_WATCH_DIR;
            String shadowDirName = args.length > 1 ? args[1] : DEFAULT_SHADOW_DIR;
            int watchInterval = args.length > 2 ? Integer.getInteger(args[2]) : DEFAULT_WATCH_INTERVAL;
            new FileChangeWatcher(Paths.get(watchDirName), Paths.get(shadowDirName), watchInterval).run();
        }
    }
    

    I recommend to use the default settings (e.g. use a source directory named "watch-dir") and play around with it for a while, watching the console output as you create and edit some text files in an editor. It helps understand the software's inner mechanics. If something goes wrong, e.g. within one 5 second rhythm a file is created but also quickly deleted again, there is nothing to copy or diff, so the program will just print a stack trace to System.err.

    这篇关于Java 7 watchservice获取文件更改偏移量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆