Java中的文件更改侦听器
Posted
技术标签:
【中文标题】Java中的文件更改侦听器【英文标题】:File changed listener in Java 【发布时间】:2010-10-04 10:30:40 【问题描述】:我希望在文件系统中的文件发生更改时收到通知。我只发现了一个轮询 lastModified File 属性的线程,显然这个解决方案不是最佳的。
【问题讨论】:
只是一个注释。当我们解决这个问题之前,我们发现大文件往往进来很慢,而轮询机制往往在它完全写入之前就发现了一个新的或更改的文件。为了解决这个问题,采用了“两口”解决方案。轮询器注意到文件已更改,但直到两次轮询看起来相同时才通知系统新/更改的文件:即已稳定。修复了很多badfile错误。 顺便说一句,Tomcat 在将大型 WAR 文件从远程源拖放到 webapps 文件夹时会遇到这个问题,并且已经这样做了很长时间。 我相信还有更优雅的方法可以做到这一点,但您可以保存文件的校验和,然后继续比较它。 【参考方案1】:之前写过一个日志文件监控器,发现每秒轮询几次单个文件的属性对系统性能的影响其实很小。
Java 7 作为 NIO.2 的一部分添加了 WatchService API
WatchService API 专为需要通知文件更改事件的应用程序而设计。
【讨论】:
我将示例视为监视目录,但是单个文件呢? @ArchimedesTrajano API 会在目录中的文件发生更改时通知您。触发的事件包括已更改文件的名称。因此,您可以处理某个或多个文件的事件并忽略其他文件。 @ArchimedesTrajano ***.com/questions/16251273/…【参考方案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();
对于单个文件,存在各种解决方案,例如:
https://dzone.com/articles/listening-to-fileevents-with-java-nio请注意,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。
【讨论】:
您能否更具体一些或发布文档链接?似乎在这上面找不到任何东西... 好像是包java.nio.file
。见tutorial。
docs.oracle.com/javase/tutorial/essential/io/notification.html【参考方案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();
【讨论】:
以上是关于Java中的文件更改侦听器的主要内容,如果未能解决你的问题,请参考以下文章