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


92

File.renameTo()看来Java 是有问题的,尤其是在Windows上。如API文档所述

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

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

这里不应该有任何文件锁定的目录中的内容被移动了,不过,很多时候,renameTo()无法完成其工作,并返回false。(我只是猜测也许某些文件锁在Windows上会任意地到期。)

当前,我有一个使用复制和删除的后备方法,但这很麻烦,因为它可能需要很多时间,具体取决于文件夹的大小。我也在考虑简单地记录一个事实,即用户可以手动移动文件夹,以免潜在地等待数小时。但是正确的方法显然是自动而快速的。

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


编辑:在这种情况下,似乎我们只是renameTo()通过考虑更多的事情而放弃使用;看到这个答案


3
您可以等待/使用JDK 7,它具有更好的文件系统支持。
akarnokd

@ kd304,实际上我等不及要使用抢先体验版,但是很高兴知道这样的事情即将到来!
约尼克(Jonik)2009年

Answers:


52

另请参阅 Files.move() JDK 7中方法。

一个例子:

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

7
不幸的是,Java7并不总是答案(就像42一样)
wuppi 2013年

1
即使在ubuntu,JDK7上,在带有EBS存储的EC2上运行代码时,我们也遇到了此问题。File.renameTo失败,File.canWrite也失败。
saurabheights

请注意,这与File#renameTo()一样不可靠。失败时只会给出一个更有用的错误。我发现的唯一合理可靠的方法是将Files#copy的文件复制到新名称,然后使用Files#delete删除原始文件(删除本身也会失败,原因与Files#move可能失败相同) 。
jwenting

26

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

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

    (显然,我不应该假定这种事情在所有平台上都可以起作用;这正是Javadoc警告的。)

  2. 如果您怀疑可能存在一些挥之不去的文件锁,请稍等片刻再进行移动/重命名可能会有所帮助。(在安装程序/升级程序的某一点上,我们添加了一个“休眠”操作和一个不确定的进度条,持续了大约10秒钟,因为某些文件可能挂着服务)。甚至可以执行一个简单的重试机制try renameTo(),然后等待一段时间(可能逐渐增加),直到操作成功或达到某个超时为止。

就我而言,大多数问题似乎都可以通过兼顾以上两项来解决,因此我们根本不需要进行本机内核调用或诸如此类的事情。


2
我现在接受我自己的答案,因为它描述了对我们的案例有帮助的内容。不过,如果有人用namedTo()为更一般的问题提出了一个不错的答案,请随时发表,我很乐意重新考虑接受的答案。
约尼克,

4
6.5年后,我认为是时候接受JDK 7回答了,特别是因为许多人认为它很有帮助。=)
约尼克(Jonik

19

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

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

它抛出IOException而不是在错误时返回布尔值false。

另请参见big lep其他线程中的响应。


2
而且,看起来JDK 1.7将包括更好的文件系统I / O支持。查看java.nio.file.Path.moveTo():java.sun.com/javase/7/docs/api/java/nio/file/Path.html
MykennaC 2010年

2
JDK 1.7没有方法java.nio.file.Path.moveTo()
Malte Schwerhoff 2015年

5

就我而言,它似乎是我自己的应用程序中的死对象,该对象保留了该文件的句柄。因此,该解决方案对我有用:

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

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

缺点:限制为20,是一些硬编码的数字。在我所有的测试中,i = 1就足够了。但可以肯定的是,我将其保留为20岁。


1
我做了类似的事情,但是循环中有100毫秒的睡眠时间。
劳伦斯·多尔

4

我知道这似乎有些骇人听闻,但是对于我一直需要的它,似乎缓冲的读者和作家在制作文件时没有问题。

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是文件位置的完整路径即可。

干杯仙人掌


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

2
嗯,即使renameTo(或destFile.delete)失败并且该方法抛出IOException,该代码也会删除srcFile。我不确定这是否是个好主意。
Jonik

1
@ Jonik,Thanx,修复了重命名失败时不删除src文件的代码。
疯狂的马

感谢您分享此问题,解决了我在Windows上的重命名问题。
BillMan 2011年

3

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

重命名* .txt * .bak

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


超级好多了!谢谢!:-)
gaffcz

2

为什么不....

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上运行,如果existFile不存在,则不执行任何操作,但是显然可以更好地进行修复。


2

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

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

1

就我而言,错误出在父目录的路径中。可能是一个错误,我不得不使用子字符串来获取正确的路径。

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

0

我知道它很烂,但是一种替代方法是创建一个bat脚本,该脚本输出诸如“ SUCCESS”或“ ERROR”之类的简单内容,调用它,等待其执行,然后检查其结果。

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

这个线程可能很有趣。还要检查Process类,以了解如何读取其他进程的控制台输出。


-2

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

Robocopy旨在可靠地镜像目录或目录树。它具有确保复制所有NTFS属性和属性的功能,并包括用于受到中断的网络连接的其他重新启动代码。


谢谢。但是由于robocopy并不是Java库,所以从我的Java代码中(捆绑并)使用它可能不是很容易...
Jonik

-2

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

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

它在kernel32.dll中定义。


1
我觉得将其包装在JNI中所遇到的麻烦要大于在进程装饰器中包装robocopy所需的工作。
凯文·蒙特罗斯

是的,这是您为抽象所付出的代价-当它泄漏时,它泄漏得很好= D
Chii 2009年

谢谢,如果它不太复杂,我可能会考虑一下。我还没有用过JNI,无法找到调用SO一个Windows内核函数的好例子,所以我张贴了这个问题:stackoverflow.com/questions/1000723/...
Jonik

您可以尝试像johannburkard.de/software/nativecall这样的通用JNI包装器,因为这是一个非常简单的函数调用。
彼得·史密斯

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

上面是简单的代码。我已经在Windows 7上进行了测试,并且工作正常。


11
在某些情况下,renameTo()无法可靠运行;这就是问题的全部。
约尼克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.