【问题标题】:File changed listener in JavaJava中的文件更改侦听器
【发布时间】:2010-10-04 10:30:40
【问题描述】:

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

【问题讨论】:

  • 只是一个注释。当我们解决这个问题之前,我们发现大文件往往进来很慢,而轮询机制往往在它完全写入之前就发现了一个新的或更改的文件。为了解决这个问题,采用了“两口”解决方案。轮询器注意到文件已更改,但直到两次轮询看起来相同时才通知系统新/更改的文件:即已稳定。修复了很多badfile错误。
  • 顺便说一句,Tomcat 在将大型 WAR 文件从远程源拖放到 webapps 文件夹时会遇到这个问题,并且已经这样做了很长时间。
  • 我相信还有更优雅的方法可以做到这一点,但您可以保存文件的校验和,然后继续比较它。

标签: java file listener


【解决方案1】:

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

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

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

【讨论】:

  • 我将示例视为监视目录,但是单个文件呢?
  • @ArchimedesTrajano API 会在目录中的文件发生更改时通知您。触发的事件包括已更改文件的名称。因此,您可以处理某个或多个文件的事件并忽略其他文件。
【解决方案2】:

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

DefaultFileMonitor

【讨论】:

    【解决方案3】:

    有一个名为jnotify 的库在Linux 上包装inotify,并且还支持Windows。没用过,不知道好不好用,不过值得一试。

    【讨论】:

    • 它运行完美,使用超级简单。我已经使用 inotify 很多年了。快速、稳定、可靠
    【解决方案4】:

    自 JDK 1.7 起,通知应用程序文件更改的规范方法是使用 WatchService API。 WatchService 是事件驱动的。 official tutorial 提供了一个例子:

    /*
     * 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 不提供确定文件是否已关闭的方法。

    【讨论】:

    • 那个 API 过于复杂。如果强制您使用“无限”循环来实现/管理线程只是为了使用它。然后你必须提防所谓的“溢出”事件,这意味着你错过了一些东西,但谁知道呢。它实际上增加了程序员的工作,因为本机目录更改通知通常会带来过度的好处(如果文件系统甚至支持,否则无论如何它默认为内部轮询)。如果它遵循一个简单的侦听器模式,那就太好了。例如:WatchService.watch(File f, FileListener listener),其中 f 可以是目录或文件。太糟糕了。
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      我每次去读取属性文件时都会运行这个sn-p代码,只有在我上次读取文件后被修改过时才会真正读取文件。希望这对某人有所帮助。

      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 中使用了类似的方法。

      【讨论】:

        【解决方案7】:

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

        更新:已添加到 Java SE 7。Chris Janicki 提供了link to the relevant Java tutorial

        【讨论】:

        【解决方案8】:

        您可以使用 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();
            }
        }
        

        【讨论】:

          【解决方案9】:

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

          https://www.teamdev.com/jniwrapper

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

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

          【讨论】:

            【解决方案10】:

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

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

            【讨论】:

              【解决方案11】:

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

              【讨论】:

                【解决方案12】:

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

                您还可以在线查看它的实际效果: http://www.teamdev.com/jxfilewatcher/onlinedemo/

                【讨论】:

                  【解决方案13】:

                  与其他答案类似,这就是我如何使用 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);
                      }
                  
                    }
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    轮询上次修改的文件属性是一个简单而有效的解决方案。只需定义一个扩展我的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();
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 2013-02-02
                      • 2012-03-17
                      • 1970-01-01
                      • 2020-01-07
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-01-07
                      相关资源
                      最近更新 更多