110

我希望在文件系统中的文件发生更改时收到通知。我只发现了一个轮询 lastModified File 属性的线程,显然这个解决方案不是最佳的。

4

14 回答 14

119

之前写过一个日志文件监控器,发现每秒轮询几次单个文件的属性对系统性能的影响其实很小。

作为 NIO.2 的一部分,Java 7 添加了WatchService API

WatchService API 专为需要通知文件更改事件的应用程序而设计。

于 2009-01-30T09:28:46.330 回答
39

我使用来自 Apache Commons 的 VFS API,下面是一个示例,说明如何在不影响性能的情况下监控文件:

默认文件监视器

于 2009-01-30T15:43:16.870 回答
27

有一个名为jnotify的库,它在 linux上包装了inotify,并且还支持 windows。从未使用过它,我不知道它有多好,但我会说值得一试。

于 2009-01-30T09:18:38.723 回答
24

从 JDK 1.7 开始,通知应用程序文件更改的规范方法是使用WatchService API。WatchService 是事件驱动的。官方教程提供了一个例子:

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;

/**
 * Example to watch a directory (or tree) for changes to files.
 */

public class WatchDir {

    private final WatchService watcher;
    private final Map<WatchKey,Path> keys;
    private final boolean recursive;
    private boolean trace = false;

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }

    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        if (trace) {
            Path prev = keys.get(key);
            if (prev == null) {
                System.out.format("register: %s\n", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %s\n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
    }

    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException
            {
                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * Creates a WatchService and registers the given directory
     */
    WatchDir(Path dir, boolean recursive) throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey,Path>();
        this.recursive = recursive;

        if (recursive) {
            System.out.format("Scanning %s ...\n", dir);
            registerAll(dir);
            System.out.println("Done.");
        } else {
            register(dir);
        }

        // enable trace after initial registration
        this.trace = true;
    }

    /**
     * Process all events for keys queued to the watcher
     */
    void processEvents() {
        for (;;) {

            // wait for key to be signalled
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }

            Path dir = keys.get(key);
            if (dir == null) {
                System.err.println("WatchKey not recognized!!");
                continue;
            }

            for (WatchEvent<?> event: key.pollEvents()) {
                WatchEvent.Kind kind = event.kind();

                // TBD - provide example of how OVERFLOW event is handled
                if (kind == OVERFLOW) {
                    continue;
                }

                // Context for directory entry event is the file name of entry
                WatchEvent<Path> ev = cast(event);
                Path name = ev.context();
                Path child = dir.resolve(name);

                // print out event
                System.out.format("%s: %s\n", event.kind().name(), child);

                // if directory is created, and watching recursively, then
                // register it and its sub-directories
                if (recursive && (kind == ENTRY_CREATE)) {
                    try {
                        if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
                            registerAll(child);
                        }
                    } catch (IOException x) {
                        // ignore to keep sample readbale
                    }
                }
            }

            // reset key and remove from set if directory no longer accessible
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);

                // all directories are inaccessible
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }

    static void usage() {
        System.err.println("usage: java WatchDir [-r] dir");
        System.exit(-1);
    }

    public static void main(String[] args) throws IOException {
        // parse arguments
        if (args.length == 0 || args.length > 2)
            usage();
        boolean recursive = false;
        int dirArg = 0;
        if (args[0].equals("-r")) {
            if (args.length < 2)
                usage();
            recursive = true;
            dirArg++;
        }

        // register directory and process its events
        Path dir = Paths.get(args[dirArg]);
        new WatchDir(dir, recursive).processEvents();
    }
}

对于单个文件,存在各种解决方案,例如:

请注意,Apache VFS 使用轮询算法,尽管它可能提供更强大的功能。另请注意,API 不提供确定文件是否已关闭的方法。

于 2009-01-30T08:37:36.200 回答
8

Java commons-io 有一个FileAlterationObserver。它结合 FileAlterationMonitor 进行轮询。类似于commons VFS。优点是它的依赖性要少得多。

编辑:较少的依赖是不正确的,它们对于 VFS 是可选的。但它使用 java File 而不是 VFS 抽象层。

于 2013-03-17T19:05:17.230 回答
5

“更多 NIO 功能”具有文件监视功能,其实现取决于底层操作系统。应该在JDK7中。

更新:已添加到 Java SE 7。Chris Janicki 提供了相关 Java 教程的链接

于 2009-01-30T11:14:43.107 回答
5

每次我去读取属性文件时,我都会运行这段代码,只有在我上次读取文件后它被修改过时才会真正读取文件。希望这可以帮助某人。

private long timeStamp;
private File file;

private boolean isFileUpdated( File file ) {
  this.file = file;
  this.timeStamp = file.lastModified();

  if( this.timeStamp != timeStamp ) {
    this.timeStamp = timeStamp;
    //Yes, file is updated
    return true;
  }
  //No, file is not updated
  return false;
}

Log4J 中使用了类似的方法FileWatchdog

于 2013-01-16T01:39:12.930 回答
2

如果您愿意花一些钱,JNIWrapper 是一个带有 Winpack 的有用库,您将能够获取某些文件的文件系统事件。不幸的是只有窗户。

请参阅https://www.teamdev.com/jniwrapper

否则,诉诸本机代码并不总是一件坏事,尤其是当提供的最佳方案是针对本机事件的轮询机制时。

我注意到 Java 文件系统操作在某些计算机上可能会很慢,如果处理不当很容易影响应用程序的性能。

于 2009-01-30T08:59:30.290 回答
2

您可以使用 FileReader 监听文件更改。请看下面的例子

// File content change listener 
private String fname;
private Object lck = new Object();
... 
public void run()
{
    try
    {
        BufferedReader br = new BufferedReader( new FileReader( fname ) );
        String s;
        StringBuilder buf = new StringBuilder();
        while( true )
        {
            s = br.readLine();
            if( s == null )
            {
                synchronized( lck )
                {
                    lck.wait( 500 );
                }
            }
            else
            {
               System.out.println( "s = " + s );
            }

        }
    }
    catch( Exception e )
    {
        e.printStackTrace();
    }
}
于 2013-07-30T13:17:56.357 回答
1

有一个名为 JxFileWatcher 的用于文件和文件夹监视的商业跨桌面库。可以从这里下载: http ://www.teamdev.com/jxfilewatcher/

你也可以在网上看到它: http ://www.teamdev.com/jxfilewatcher/onlinedemo/

于 2009-05-18T09:51:28.023 回答
1

您还可以考虑 Apache Commons JCI(Java 编译器接口)。尽管这个 API 似乎专注于类的动态编译,但它还在其 API 中包含了用于监视文件更改的类。

示例: http ://commons.apache.org/jci/usage.html

于 2010-02-08T22:34:16.673 回答
1

Spring Integration 提供了一个很好的机制来查看目录和文件:http ://static.springsource.org/spring-integration/reference/htmlsingle/#files 。很确定它是跨平台的(我在 mac、linux 和 windows 上使用过)。

于 2012-05-10T15:29:49.330 回答
1

轮询最后修改的文件属性是一个简单而有效的解决方案。只需定义一个扩展我的类FileChangedWatcher并实现该onModified()方法:

import java.io.File;

public abstract class FileChangedWatcher
{
    private File file;

    public FileChangedWatcher(String filePath)
    {
        file = new File(filePath);
    }

    public void watch() throws InterruptedException
    {
        long currentModifiedDate = file.lastModified();

        while (true)
        {
            long newModifiedDate = file.lastModified();

            if (newModifiedDate != currentModifiedDate)
            {
                currentModifiedDate = newModifiedDate;
                onModified();
            }

            Thread.sleep(100);
        }
    }

    public String getFilePath()
    {
        return file.getAbsolutePath();
    }

    protected abstract void onModified();
}
于 2015-10-28T13:46:37.713 回答
1

与其他答案类似,这就是我如何使用 File、Timer 和 TimerTask 让它作为后台线程以设定的间隔轮询运行。

import java.io.File;
import java.util.Timer;
import java.util.TimerTask;

public class FileModifiedWatcher
{
  private static File file;
  private static int pollingInterval;
  private static Timer fileWatcher;
  private static long lastReadTimeStamp = 0L;

  public static boolean init(String _file, int _pollingInterval)
  {
    file =  new File(_file);
    pollingInterval = _pollingInterval; // In seconds

    watchFile();

    return true;
  }

  private static void watchFile()
  {
    if ( null == fileWatcher )
    {
      System.out.println("START");

      fileWatcher = new Timer();

      fileWatcher.scheduleAtFixedRate(new TimerTask()
      {
        @Override
        public void run()
        {

          if ( file.lastModified() > lastReadTimeStamp )
          {
            System.out.println("File Modified");
          }

          lastReadTimeStamp = System.currentTimeMillis();
        }
      }, 0, 1000 * pollingInterval);
    }

  }
}
于 2016-01-06T22:34:12.370 回答