如何在Windows上用Java创建 - 然后 - 原子重命名文件?

Pio*_*sen 7 java windows file-io atomicity

我正在尝试在Windows上使用Java 正确实现"写临时文件并重命名" .

如何在Java中以原子方式重命名文件,即使dest文件已存在?建议重命名文件是"原子操作"(无论"原子"实际上是什么意思). /sf/answers/1439967791/建议编写tmp文件并重命名是跨平台的,并确保最终文件不存在或可由其他进程处理.

所以我试着实际实现这种方法.以下是我的尝试摘要.对于实际问题 - 跳到底部.

写方法

我试着写和重命名文件的各种方法(contentcharsetStringCharset分别对应).

使用java.nio.file.Files:

Files.copy(new ByteArrayInputStream(content.getBytes(charset)), tmpFile);
Files.move(tmpFile, finalFile, StandardCopyOption.ATOMIC_MOVE);
Run Code Online (Sandbox Code Playgroud)

使用番石榴(14)和java.io.File:

com.google.common.io.Files.write(content, tmpFile, charset);
tmpFile.renameTo(finalFile);
Run Code Online (Sandbox Code Playgroud)

或者甚至更模糊的方法:

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();
Run Code Online (Sandbox Code Playgroud)

阅读方法

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

具有共同功能:

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

使用java.nio.file.Files:

waitUntilExists();
return new String(Files.readAllBytes(finalFile), charset);
Run Code Online (Sandbox Code Playgroud)

使用番石榴(14):

waitUntilExists();
return new String(com.google.common.io.Files.toByteArray(finalFile.toFile()), charset);
Run Code Online (Sandbox Code Playgroud)

或者甚至更模糊的方法:

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();
Run Code Online (Sandbox Code Playgroud)

结果

如果我使用" 方法" 阅读java.nio.file.Files,一切都运行正常.

如果我在Linux上运行此代码(超出此问题的范围,我知道),一切正常.

但是,如果我用Guava 实现读取,或者FileInputStream可能性高于0.5%(0.005),则测试失败

java.io.FileNotFoundException:进程无法访问该文件,因为它正由另一个进程使用

(由我自己翻译的消息导致我的窗口不是英文;提到"另一个进程"是误导性的,因为即使这是相同的过程,Windows也是正常的,我用明确的阻止验证了.)

如何在Windows上使用Java实现create-then-rename,以便最终文件以原子方式显示,即要么不存在,要么可以读取?

因为我对进程的控制比拾取文件所控制的,我不能假设使用任何特定的读取方法,或者甚至它们都是Java.因此,该解决方案应适用于上面列出的所有读取方法.

Pio*_*sen 0

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

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

维基百科上的文件锁定

对于在 Windows 中使用文件读/写 API 的应用程序,字节范围锁由 Windows 中执行的文件系统强制执行(也称为强制锁)。对于在 Windows 中使用文件映射 API 的应用程序,不强制执行字节范围锁(也称为咨询锁)。

虽然维基百科不是 Windows 的文档,但这仍然提供了一些线索。

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