Java 7 watchservice获取文件更改偏移量 [英] Java 7 watchservice get file change offset
问题描述
我刚刚使用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>
生产环境存在一些限制因素:
- 为了不使示例代码复杂化,必须在创建影子目录时复制子目录(因为我已经回收了现有方法来创建深层目录副本),但是在运行时忽略。只监视被监视目录下面的文件,以避免递归。
- 不满足您不使用外部库的要求,因为我真的想避免重新发明轮子以进行统一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屋!