如何在 Windows 上的 Java 中创建然后自动重命名文件?

How to create-then-atomically-rename file in Java on Windows?

我正在尝试在 Windows 上使用 Java 正确实施 "write temporary file and rename"

How to atomically rename a file in Java, even if the dest file already exists? 建议重命名文件是 "atomic operation"(无论 "atomic" 实际意味着什么)。 建议编写 tmp 文件并重命名是跨平台的,并确保最终文件不存在或可以由其他进程处理。

所以我尝试实际实施这种方法。以下是我的尝试总结。对于实际问题——跳到底部。

编写方法

我尝试了各种写入和重命名文件的方式(contentcharset分别是StringCharset):

使用java.nio.file.Files

Files.copy(new ByteArrayInputStream(content.getBytes(charset)), tmpFile);
Files.move(tmpFile, finalFile, StandardCopyOption.ATOMIC_MOVE);

使用 Guava (14) 和 java.io.File:

com.google.common.io.Files.write(content, tmpFile, charset);
tmpFile.renameTo(finalFile);

或者更晦涩的方法:

try (OutputStream os = new FileOutputStream(tmpFile);
        Writer writer = new OutputStreamWriter(os, charset)) {
    writer.write(content);
}
Runtime.getRuntime().exec(
        new String[] { "cmd.exe", "/C", "move " + tmpFile + " " + finalFile }).waitFor();

阅读方法

现在假设另一个线程(线程因为我在测试中,在现实生活中它可能是另一个进程)正在执行以下代码版本之一:

常用功能:

void waitUntilExists() throws InterruptedException {
    while (!java.nio.file.Files.exists(finalFile)) {
        NANOSECONDS.sleep(1);
    }
}

使用java.nio.file.Files

waitUntilExists();
return new String(Files.readAllBytes(finalFile), charset);

使用 Guava (14):

waitUntilExists();
return new String(com.google.common.io.Files.toByteArray(finalFile.toFile()), charset);

或者更晦涩的方法:

waitUntilExists();
StringBuilder sb = new StringBuilder();
try (InputStream is = new FileInputStream(finalFile.toFile())) {
    byte[] buf = new byte[8192];
    int n;
    while ((n = is.read(buf)) > 0) {
        sb.append(new String(buf, 0, n, charset));
    }
}
return sb.toString();

结果

如果我 阅读 使用“java.nio.file.Files 方法”,一切正常。

如果我 运行 Linux 上的这段代码(我知道这超出了这个问题的范围),一切正常。

但是,如果我使用 Guava 或 FileInputStream 实现 read,则测试失败的可能性高于 0.5% (0.005),并显示

java.io.FileNotFoundException: Process cannot access the file, because it is being used by another process

(我自己翻译的消息因为我的 windows 不是英文;参考 "another process" 是误导,因为 Windows 告诉这个是正常的,即使这是相同的进程,我通过显式阻止进行了验证。)

问题

如何在 Windows 上使用 Java 实现创建然后重命名,以便最终文件自动出现,即要么不存在,要么可以读取?

因为我确实可以控制进程而不是获取文件,所以我不能假设正在使用任何特定的阅读方法,甚至不能假设它们在 Java 中。因此,该解决方案应该适用于上面列出的所有读取方法。

这似乎就是 Windows/NTFS 的行为方式。

此外,使用旧 IO 和 NIO 读取的行为差异可能是因为它们使用不同的 Windows API。

Wikipedia on File locking

For applications that use the file read/write APIs in Windows, byte-range locks are enforced (also referred to as mandatory locks) by the file systems that execute within Windows. For applications that use the file mapping APIs in Windows, byte-range locks are not enforced (also referred to as advisory locks.)

虽然维基百科不是 Windows 的文档,但这仍然能说明一些问题。

(我给出这个答案只是为了让其他 有相同想法的人 不必写这个。真实的答案,参考文档或报告的错误,非常感谢。 )

JDK 中的 java.io.File.renameTo() 函数有错误报告,它在 Windows 上不是原子的,已关闭,无法修复:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4017593。 所以可能没有干净的方法来解决你的问题。