在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); } 

对于什么是值得的,一些进一步的概念:

  1. 在Windows上,如果目标目录存在, renameTo()似乎会失败,即使它是空的。 这让我感到吃惊,因为我曾经在Linux上尝试过,如果目标存在,那么renameTo()成功,只要它是空的。

    (很明显,我不应该认为这种事情在跨平台上是一样的,这正是Javadoc所警告的。)

  2. 如果您怀疑可能有一些滞留的文件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过,效果很好。