如何在 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 文件并重命名是跨平台的,并确保最终文件不存在或可以由其他进程处理。
所以我尝试实际实施这种方法。以下是我的尝试总结。对于实际问题——跳到底部。
编写方法
我尝试了各种写入和重命名文件的方式(content
和charset
分别是String
和Charset
):
使用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。
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。
所以可能没有干净的方法来解决你的问题。
我正在尝试在 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 文件并重命名是跨平台的,并确保最终文件不存在或可以由其他进程处理。
所以我尝试实际实施这种方法。以下是我的尝试总结。对于实际问题——跳到底部。
编写方法
我尝试了各种写入和重命名文件的方式(content
和charset
分别是String
和Charset
):
使用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。
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。 所以可能没有干净的方法来解决你的问题。