在Windows上可靠的File.renameTo()替代?
Java的File.renameTo()
是有问题的,特别是在Windows上,似乎。 正如API文档所述 ,
这种方法的许多方面的行为本质上是依赖于平台的:重命名操作可能无法将文件从一个文件系统移动到另一个文件系统,它可能不是primefaces的,并且如果具有目标抽象path名的文件可能不成功已经存在。 应始终检查返回值,以确保重命名操作成功。
就我而言,作为升级过程的一部分,我需要移动(重命名)一个可能包含千兆字节数据的目录(大量子目录和不同大小的文件)。 此举总是在同一分区/驱动器内完成的,因此不需要物理移动磁盘上的所有文件。
不应该有任何文件locking目录的内容被移动,但仍然经常renameTo()无法完成其工作,并返回false。 (我只是猜测,也许某些文件locking在Windows上有些任意地过期。)
目前我有一个使用复制和删除的回退方法,但这很糟糕,因为它可能需要很长的时间,这取决于文件夹的大小。 我还在考虑简单地logging一下这样一个事实,即用户可以手动移动文件夹以避免等待几个小时。 但是正确的做法显然是自动而快速的。
所以我的问题是, 你是否知道另一种可靠的方法来在Windows上快速移动/重命名Java ,无论是使用普通的JDK还是外部库。 或者,如果你知道一个简单的方法来检测和释放给定文件夹及其所有内容 (可能是成千上万个文件)的任何文件locking,那也可以。
编辑 :在这个特殊的情况下,似乎我们通过考虑几个更多的东西而使用了renameTo()
。 看到这个答案 。
另请参阅JDK 7中的Files.move()
方法。
一个例子:
String fileName = "MyFile.txt"; try { Files.move(new File(fileName).toPath(), new File(fileName).toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { Logger.getLogger(SomeClass.class.getName()).log(Level.SEVERE, null, ex); }
对于什么是值得的,一些进一步的概念:
-
在Windows上,如果目标目录存在,
renameTo()
似乎会失败,即使它是空的。 这让我感到吃惊,因为我曾经在Linux上尝试过,如果目标存在,那么renameTo()
成功,只要它是空的。(很明显,我不应该认为这种事情在跨平台上是一样的,这正是Javadoc所警告的。)
-
如果您怀疑可能有一些滞留的文件locking,在移动/重命名之前稍等一下可能会有所帮助。 (在我们的安装程序/升级程序中,我们添加了一个“睡眠”动作和一个不确定的进度条,大约10秒钟,因为可能有服务挂在某些文件上)。 也许甚至做一个简单的重试机制,试图
renameTo()
,然后等待一段时间(可能会逐渐增加),直到操作成功或达到一些超时。
就我而言,大多数问题似乎已经通过考虑上述两个问题得到了解决,所以我们不需要进行本地内核调用,或者其他一些事情。
最初的文章要求“在Windows上使用Java进行快速移动/重命名的替代可靠方法,无论是使用普通的JDK还是外部库”。
这里没有提到的另一个选项是apache.commons.io库的v1.3.2或更高版本,其中包括FileUtils.moveFile() 。
它抛出一个IOException,而不是在错误时返回布尔值false。
另请参阅big lep在其他主题中的回应。
下面这段代码不是“替代”,但是在Windows和Linux环境下都可以为我工作:
public static void renameFile(String oldName, String newName) throws IOException { File srcFile = new File(oldName); boolean bSucceeded = false; try { File destFile = new File(newName); if (destFile.exists()) { if (!destFile.delete()) { throw new IOException(oldName + " was not successfully renamed to " + newName); } } if (!srcFile.renameTo(destFile)) { throw new IOException(oldName + " was not successfully renamed to " + newName); } else { bSucceeded = true; } } finally { if (bSucceeded) { srcFile.delete(); } } }
我知道这似乎有点冒险,但对于我一直需要它,似乎缓冲读者和作家没有问题的文件。
void renameFiles(String oldName, String newName) { String sCurrentLine = ""; try { BufferedReader br = new BufferedReader(new FileReader(oldName)); BufferedWriter bw = new BufferedWriter(new FileWriter(newName)); while ((sCurrentLine = br.readLine()) != null) { bw.write(sCurrentLine); bw.newLine(); } br.close(); bw.close(); File org = new File(oldName); org.delete(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
适用于小文本文件作为parsing器的一部分,只要确保oldName和newName是文件位置的完整path。
干杯Kactus
就我而言,它似乎是我自己的应用程序中的一个死对象,它保留了该文件的句柄。 所以这个解决scheme为我工作:
for (int i = 0; i < 20; i++) { if (sourceFile.renameTo(backupFile)) break; System.gc(); Thread.yield(); }
优点:它非常快,因为没有Thread.sleep()具有特定的硬编码时间。
缺点:20的限制是一些硬编码的数字。 在我所有的testing中,i = 1就足够了。 但是可以肯定的是,我把它留在了20。
在窗口上,我使用Runtime.getRuntime().exec("cmd \\c ")
,然后使用命令行重命名function来实际重命名文件。 这是更灵活的,例如,如果你想重命名扩展的所有文本文件在一个目录bak只是写这个输出stream:
重命名* .txt * .bak
我知道这不是一个好的解决scheme,但显然它一直为我工作,比Java内联支持更好。
为什么不….
import com.sun.jna.Native; import com.sun.jna.Library; public class RenamerByJna { /* Requires jna.jar to be in your path */ public interface Kernel32 extends Library { public boolean MoveFileA(String existingFileName, String newFileName); } public static void main(String[] args) { String path = "C:/yourchosenpath/"; String existingFileName = path + "test.txt"; String newFileName = path + "renamed.txt"; Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class); kernel32.MoveFileA(existingFileName, newFileName); } }
在nwindows 7上工作,如果存在的文件不存在,则什么也不做,但显然可以更好地解决这个问题。
在我的情况下,错误是在父目录的path。 也许是一个错误,我不得不使用子string来获得正确的path。
try { String n = f.getAbsolutePath(); **n = n.substring(0, n.lastIndexOf("\\"));** File dest = new File(**n**, newName); f.renameTo(dest); } catch (Exception ex) { ...
我有一个类似的问题。 文件被复制,而不是在Windows上移动,但在Linux上运行良好。 我通过在调用renameTo()之前closures打开的fileInputStream来解决问题。 在Windows XP上testing。
fis = new FileInputStream(originalFile); .. .. .. fis.close();// <<<---- Fixed by adding this originalFile.renameTo(newDesitnationForOriginalFile);
我知道它很糟糕,但另一种方法是创build一个bat脚本,输出一些简单的“SUCCESS”或“ERROR”,调用它,等待它被执行,然后检查其结果。
Runtime.getRuntime()。exec(“cmd / c start test.bat”);
这个线程可能很有趣。 请检查Process类以了解如何读取不同进程的控制台输出。
你可以尝试robocopy 。 这不完全是“重命名”,但它是非常可靠的。
Robocopy专为可靠的镜像目录或目录树而devise。 它具有确保所有NTFS属性和属性都被复制的function,并包含用于受到中断的networking连接的附加重启代码。
要移动/重命名文件,您可以使用此function:
BOOL WINAPI MoveFile( __in LPCTSTR lpExistingFileName, __in LPCTSTR lpNewFileName );
它在kernel32.dll中定义。
File srcFile = new File(origFilename); File destFile = new File(newFilename); srcFile.renameTo(destFile);
以上是简单的代码。 我在Windows 7上testing过,效果很好。