Windows上可靠的File.renameTo()替代方案?

时间:2009-06-16 08:29:29

标签: java windows file file-io

Java的File.renameTo()是有问题的,特别是在Windows上,似乎。 正如API documentation所说,

  

这种行为的许多方面   方法本质上   依赖于平台:重命名   操作可能无法移动   从一个文件系统到另一个文件系统,   它可能不是原子的,它可能   如果一个文件与。不成功   目的地抽象路径名已经   存在。返回值应始终   检查以确保   重命名操作成功。

就我而言,作为升级过程的一部分,我需要移动(重命名)一个可能包含千兆字节数据的目录(许多子目录和不同大小的文件)。移动总是在同一个分区/驱动器内完成,因此不需要实际移动磁盘上的所有文件。

不应该对要移动的目录内容的任何文件锁定,但是,通常,renameTo()仍然无法完成其工作并返回false。 (我只是猜测某些文件锁可能会在Windows上随意过期。)

目前我有一个使用复制& amp;的回退方法删除,但这很糟糕,因为它可能需要很多的时间,具体取决于文件夹的大小。我还在考虑简单地记录用户可以手动移动文件夹以避免等待数小时的事实。但正确的方式显然是自动而快速的。

所以我的问题是,您是否知道使用普通JDK或某些外部库在Windows上使用Java快速移动/重命名的另一种可靠方法。或者,如果您知道 easy 方法来检测和释放给定文件夹的任何文件锁以及其所有内容(可能是数千个单独的文件),那也没关系


修改:在这种特殊情况下,我们似乎只是考虑了一些事情而只使用renameTo();见this answer

15 个答案:

答案 0 :(得分:45)

另请参阅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 :(得分:25)

对于它的价值,还有其他一些概念:

  1. 在Windows上,如果目标目录存在,renameTo()似乎失败,即使它是空的。这让我感到惊讶,正如我在Linux上所尝试的那样,只要目标存在,renameTo()就会成功,只要它是空的。

    (显然我不应该假设这种事情在各个平台上都是一样的;这正是Javadoc所警告的。)

  2. 如果您怀疑可能存在一些延迟文件锁定,请在移动/重命名之前稍等一下帮助。 (在我们的安装程序/升级程序的一点上,我们添加了一个“睡眠”操作和一个不确定的进度条大约10秒钟,因为某些文件可能会挂起一个服务)。也许甚至做一个简单的重试机制,尝试renameTo(),然后等待一段时间(可能会逐渐增加),直到操作成功或达到一些超时。

  3. 在我的情况下,大多数问题似乎已经通过考虑上述两个问题得到解决,所以我们不需要做本机内核调用,或者毕竟是一些这样的事情。

答案 2 :(得分:19)

原始帖子要求“使用普通JDK或某些外部库在Windows上使用Java快速移动/重命名的另一种可靠方法。”

此处未提及的另一个选项是apache.commons.io库的v1.3.2或更高版本,其中包含FileUtils.moveFile()

抛出IOException而不是在出错时返回布尔值假。

另请参阅this other thread big lep 的回复。

答案 3 :(得分:4)

以下代码不是“替代”,但在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();
          }
    }
}

答案 4 :(得分:4)

在我的情况下,它似乎是我自己的应用程序中的一个死对象,它保留了该文件的句柄。所以这个解决方案对我有用:

for (int i = 0; i < 20; i++) {
    if (sourceFile.renameTo(backupFile))
        break;
    System.gc();
    Thread.yield();
}

优点:它非常快,因为没有Thread.sleep()具有特定的硬编码时间。

缺点:20的限制是一些硬编码。在我的所有测试中,i = 1就足够了。但是要确保我把它留在了20。

答案 5 :(得分:3)

我知道这看起来有点像hacky,但是对于我一直需要的东西,似乎缓冲的读者和作者都没有问题制作文件。

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

}

适用于作为解析器一部分的小文本文件,只需确保oldName和newName是文件位置的完整路径。

干杯 Kactus

答案 6 :(得分:2)

在Windows上我使用Runtime.getRuntime().exec("cmd \\c ")然后使用命令行重命名功能来实际重命名文件。它更加灵活,例如,如果你想将dir中所有txt文件的扩展名重命名为bak,只需将其写入输出流:

重命名* .txt * .bak

我知道这不是一个好的解决方案,但显然它一直对我有用,比Java内联支持要好得多。

答案 7 :(得分:1)

为什么不......

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

适用于Windows 7,如果现有文件不存在则不执行任何操作,但显然可以更好地检测这一点。

答案 8 :(得分:1)

就我而言,错误发生在父目录的路径中。也许是一个错误,我不得不使用子字符串来获得正确的路径。

        try {
            String n = f.getAbsolutePath();
            **n = n.substring(0, n.lastIndexOf("\\"));**
            File dest = new File(**n**, newName);
            f.renameTo(dest);
        } catch (Exception ex) {
           ...

答案 9 :(得分:1)

我有类似的问题。文件被复制而不是在Windows上移动但在Linux上运行良好。我通过在调用renameTo()之前关闭打开的fileInputStream来解决问题。在Windows XP上测试。

fis = new FileInputStream(originalFile);
..
..
..
fis.close();// <<<---- Fixed by adding this
originalFile.renameTo(newDesitnationForOriginalFile);

答案 10 :(得分:1)

好吧,我找到了一个非常直接的解决方案 -

boolean retVal = targetFile.renameTo(new File("abcd.xyz"));
while(!retVal) {
    retVal= targetFile.renameTo(new File("abcd.xyz"));
}

正如 Argeman 所建议的,您可以放置​​一个计数器并限制 while 循环运行的次数,以便在另一个 Windows 进程正在使用某些文件时它不会进入无限循环。

int counter = 0;
boolean retVal = targetFile.renameTo(new File("abcd.xyz"));
while(!retVal && counter <= 10) {
        retVal = targetFile.renameTo(new File("abcd.xyz"));
        counter = counter + 1;
}

答案 11 :(得分:0)

我知道这很糟糕,但另一种方法是创建一个bat脚本,输出一些简单的“SUCCESS”或“ERROR”,调用它,等待它执行然后检查它的结果。

Runtime.getRuntime()。exec(“cmd / c start test.bat”);

这个帖子可能很有趣。还要检查Process类,了解如何读取不同进程的控制台输出。

答案 12 :(得分:-2)

您可以尝试robocopy。这不是“重命名”,但它非常可靠。

  

Robocopy旨在实现目录或目录树的可靠镜像。它具有确保复制所有NTFS属性和属性的功能,并包含可能中断的网络连接的其他重启代码。

答案 13 :(得分:-2)

要移动/重命名文件,您可以使用此功能:

BOOL WINAPI MoveFile(
  __in  LPCTSTR lpExistingFileName,
  __in  LPCTSTR lpNewFileName
);

它在kernel32.dll中定义。

答案 14 :(得分:-8)

 File srcFile = new File(origFilename);
 File destFile = new File(newFilename);
 srcFile.renameTo(destFile);

以上是简单的代码。我已经在Windows 7上测试过并且工作得很好。