我一直在尝试使用 Java 7 WatchService 来监视文件的更改。
这是我编写的一些代码:
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;
}
}
这似乎有效,我收到有关文件“changethis.txt”何时被修改的通知。
但是,除了能够在文件更改时发出通知之外,是否还有关于文件中发生修改的位置的通知?
我查看了 Java 文档,但似乎找不到任何内容。
这是否可以使用 WatchService,或者是否必须实现一些自定义的东西?
谢谢
最佳答案
为了它的值(value),我已经破解了一些概念证明,它能够
有几个限制会成为生产环境中的障碍:
tail -f 这样的文件末尾 - 也是它最大的缺点:每当文件更改它时必须完全影子复制,否则程序无法检测到后续更改。所以我不建议将此解决方案用于非常大的文件。如何构建:
<?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>
源代码(抱歉,有点冗长):
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();
}
}
我建议使用默认设置(例如,使用名为“watch-dir”的源目录)并尝试一段时间,在编辑器中创建和编辑一些文本文件时观察控制台输出。它有助于理解软件的内部机制。如果出现问题,例如在 5 秒的节奏内创建了一个文件,但又很快又将其删除,没有要复制或比较的内容,因此程序将打印堆栈跟踪到 System.err。
关于Java 7 watchservice 获取文件更改偏移量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19480813/
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只