Java 7 WatchService - 因为另一个进程正在使用该文件,所以无法访问该文件

8
我会尽力为您翻译中文。以下是需要翻译的内容:

我按照 Java7 nio2教程 中关于监视目录更改的内容,使用 WatchDir.java 代码示例递归监视整个目录的内容。

代码如下:

// Get list of events for the watch key.
for (WatchEvent<?> event : key.pollEvents()) {
// This key is registered only for ENTRY_CREATE events, but an OVERFLOW event 
// can occur regardless if events are lost or discarded.
if (event.kind() == OVERFLOW) {
    continue;
}

// Context for directory entry event is the file name of entry.
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path fileName = ev.context();
Path fullPath = dir.resolve(fileName);

try {
    // Print out event.
    System.out.print("Processing file: " + fileName);

    processed = fileProcessor.processFile(fullPath);

    System.out.println("Processed = " + processed);

    if (processed) {
        // Print out event.
        System.out.println(" - Done!");
    }
} 
catch (FileNotFoundException e) {
    System.err.println("Error message: " + e.getMessage());
}
catch (IOException e) {
    System.err.println("Error processing file: " + fileName.toString());
    System.err.println("Error message: " + e.getMessage());
}

好的,问题(我肯定是在做一些愚蠢的事情)就在这里:

processed = fileProcessor.processFile(fullPath);

它的作用类似于这样:

public synchronized boolean processFile(Path fullPath) throws IOException {
String line;
String[] tokens;
String fileName = fullPath.getFileName().toString();
String fullPathFileName = fullPath.toString();

// Create the file.
File sourceFile = new File(fullPath.toString());

// If the file does not exist, print out an error message and return.
if (sourceFile.exists() == false) {
    System.err.println("ERROR: " + fullPathFileName + ": No such file");
    return false;
}

// Check file extension.
if (!getFileExtension(fullPathFileName).equalsIgnoreCase("dat")) {
    System.out.println(" - Ignored.");
    return false;
}

// Process source file.
try (BufferedReader bReader = new BufferedReader(new FileReader(sourceFile))) {
    int type;

    // Process each line of the file.
    while (bReader.ready()) {

        // Get a single line.
        line = bReader.readLine();

        // Get line tokens.
        tokens = line.split(delimiter);

        // Get type.
        type = Integer.parseInt(tokens[0]);

        switch (type) {
        // Type 1 = Salesman.
        case 1:
            -> Call static method to process tokes.
            break;
        // Type 2 = Customer.
        case 2:
            -> Call static method to process tokes.
            break;
        // Type 3 = Sales.
        case 3:
            -> Call static method to process tokes.
            break;
        // Other types are unknown!
        default:
            System.err.println("Unknown type: " + type);
            break;
        }
    }

    PrintStream ps = null;
    try {

        // Write output file.
        // Doesn't matter. 

    } 
    finally {               
        if (ps != null) {
            ps.close();
        }
    }
    return true;
}
}

第一次处理事件时,一切都很好!即使有多个文件需要处理。但在后续处理中,我收到了以下错误消息:
“由于另一个进程正在使用该文件,因此无法访问该文件。”
我做错了什么?我该怎么做才能成功处理连续的文件?
我忘记提及两个重要说明:
1. 我正在使用Windows 7。 2. 当我以调试模式运行应用程序时,它可以正常工作。
编辑:如果在尝试使用文件之前添加延迟,它将正常工作:
Thread.sleep(500);

// Process source file.
try (BufferedReader bReader = new BufferedReader(new FileReader(sourceFile))) {

那么,Windows有可能没有及时解锁文件吗?我该如何以正确的方式修复它?
4个回答

18

好的,我找到了一个解决方案。我不知道这是否是做这件事情的最佳方式,但它能够奏效。 不幸的是,即使文件仍被Windows锁定,file.canRead()和file.canWrite()都会返回true。因此,我发现如果我尝试使用相同的名称“重命名”它,我就知道Windows是否正在处理它。所以这就是我做的:

    while(!sourceFile.renameTo(sourceFile)) {
        // Cannot read from file, windows still working on it.
        Thread.sleep(10);
    }

我不确定依赖这个是否安全。Oracle有一天可能会在renameTo()方法中添加一个检查,如果您试图将文件重命名为自身,则提前退出是完全合法的。然后您的解决方法将不再起作用。 - peterh
1
Peterh,你不应该把“work around”作为解决问题的终极答案,这也是它的名字。 ;) 这个解决方案是在Java仍然由Sun Microsystems控制的时候完成的。如果现在有更好的答案,请分享。这就是Stack Overflow的神奇之处。 - jfajunior

2

Arundev提供的解决方案对我很有用。

我需要添加睡眠时间,否则它不能处理多个请求,并且会出现错误“该进程无法访问文件,因为它正在被另一个进程使用”。

if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

// now do your intended jobs ... 

1
我有类似的问题。但是当我的文件被复制时并没有被锁定。如果我在创建事件之后立即尝试读取它,那么它会是空的。
我认为在您的情况下不需要技巧,您可以简单地处理错误,如果出现错误,则可以执行睡眠操作,然后再次尝试。但对于我来说,这不适用。我没有错误。所以我尝试了您的解决方案,并且我明白它对我也不可接受,因为我除了读取之外没有权限。所以我可以提供我的“技巧”。我正在使用java.io.RandomAccessFile类:
List<String> lines = null;

while(true){
    try( RandomAccessFile raf = new RandomAccessFile( fileFullPath.toFile() , "r" ) ){
        lines = Files.readAllLines( fileFullPath , Charset.forName(ENCODING) );
        break;
    }catch(FileNotFoundException ex){
        LOG.warn("File isn't yet completed: {}",ex.getMessage() );
        LOG.info( "Waiting {} for next attempt to read it" , SLEEP_INTERVAL_READING );
        try {
            Thread.sleep(SLEEP_INTERVAL_READING);
        } catch (InterruptedException e) {
            LOG.error("Waiting was interrupted",e);
        }
    }catch(Exception ex){
        LOG.error("Error in reading file ",ex);
        clear();
        return false;
    }
} 

0

当事件类型为ENTRY_MODIFY时,请允许当前线程休眠几秒钟。我无法确定为什么会发生这种情况,但有时我的日志中会打印多个ENTRY_MODIFY事件。请根据您的文件大小设置休眠时间。

if(kind ==  StandardWatchEventKinds.ENTRY_MODIFY){
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
//toDo - something
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接