我正在尝试在windows上正确地使用java实现“编写临时文件并重命名”。
如何在java中自动重命名文件,即使dest文件已经存在?建议重命名文件是“原子操作”(不管“原子”实际上意味着什么)。https://stackoverflow.com/a/20570968/65458 建议编写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
,则当可能性高于0.5%(0.005)时,测试失败
java.io.filenotfoundexception:进程无法访问该文件,因为另一个进程正在使用该文件
(由于我的Windows不是英文,所以我自己翻译的信息;提到“另一个进程”是有误导性的,因为对于windows来说,即使这是同一个进程,也很正常(我用显式阻塞验证了)
问题
如何在windows上使用java实现create-then-rename,使最终文件以原子方式出现,即不存在或可以读取?
由于我确实可以控制进程而不是提取文件,所以我不能假设正在使用任何特定的读取方法,甚至不能假设它们是在java中。因此,解决方案应该与上面列出的所有读取方法一起工作。
2条答案
按热度按时间jpfvwuh41#
这似乎就是windows/ntfs的行为方式。
此外,使用旧io和nio的读取之间的行为差异可能是因为它们使用不同的windows API。
维基百科在文件锁定上说
对于在windows中使用文件读/写API的应用程序,字节范围锁由在windows中执行的文件系统强制执行(也称为强制锁)。对于在windows中使用文件MapAPI的应用程序,不强制执行字节范围锁(也称为建议锁)
虽然wikipedia不是windows的文档,但这仍然有一些启示。
)我写这个答案只是为了让其他有同样想法的人不必写这个。正确的答案,参考文档或报告的错误,非常感谢。)
zzwlnbp82#
jdk中的java.io.file.renameto()函数存在错误报告,该函数在已关闭的windows上不是原子函数,无法修复:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4017593. 所以可能没有一个干净的方法来解决你的问题。