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

Java 的 File.renameTo()似乎有问题,尤其是在 Windows 上。 正如 API 文档所说,

这种行为的许多方面 方法是固有的 与平台相关的: 重命名 操作可能无法移动 文件从一个文件系统传递到另一个文件系统, 也许不是原子弹,也许是 属性的文件不能成功 目的地抽象路径名 返回值应该总是 检查,以确保 重命名操作成功。

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

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

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

所以我的问题是,你知道在 Windows 上用 Java 实现快速移动/重命名的另一种可靠方法吗,要么使用普通的 JDK,要么使用一些外部库。或者,如果您知道一种 放松方法来检测和释放给定文件夹和 里面所有的东西(可能有数千个单独的文件)的任何文件锁,那也是可以的。


编辑 : 在这个特殊的例子中,通过考虑更多的事情,我们似乎只使用了 renameTo(); 参见 这个答案

80153 次浏览

You may try robocopy. This is not exactly "renaming", but it's very reliable.

Robocopy is designed for reliable mirroring of directories or directory trees. It has features to ensure all NTFS attributes and properties are copied, and includes additional restart code for network connections subject to disruption.

To move/rename a file you can use this function:

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

It is defined in kernel32.dll.

I know it sucks, but an alternative is to create a bat script which outputs something simple like "SUCCESS" or "ERROR", invoke it, wait for it to be executed and then check its results.

Runtime.getRuntime().exec("cmd /c start test.bat");

This thread may be interesting. Check also the Process class on how to read the console output of a different process.

For what it's worth, some further notions:

  1. On Windows, renameTo() seems to fail if the target directory exists, even if it's empty. This surprised me, as I had tried on Linux, where renameTo() succeeded if target existed, as long as it was empty.

    (Obviously I shouldn't have assumed this kind of thing works the same across platforms; this is exactly what the Javadoc warns about.)

  2. If you suspect there may be some lingering file locks, waiting a little before the move/rename might help. (In one point in our installer/upgrader we added a "sleep" action and an indeterminate progress bar for some 10 seconds, because there might be a service hanging on to some files). Perhaps even do a simple retry mechanism that tries renameTo(), and then waits for a period (which maybe increases gradually), until the operation succeeds or some timeout is reached.

In my case, most problems seem to have been solved by taking both of the above into account, so we won't need to do a native kernel call, or some such thing, after all.

The original post requested "an alternative, reliable approach to do a quick move/rename with Java on Windows, either with plain JDK or some external library."

Another option not mentioned yet here is v1.3.2 or later of the apache.commons.io library, which includes FileUtils.moveFile().

It throws an IOException instead of returning boolean false upon error.

See also big lep's response in this other thread.

I know this seems a little hacky, but for what I've been needing it for, it seems buffered readers and writers have no issue making the files.

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


}

Works well for small text files as part of a parser, just make sure oldName and newName are full paths to the file locations.

Cheers Kactus

The following piece of code is NOT an 'alternative' but has reliably worked for me on both Windows and Linux environments:

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

See also the Files.move() method in JDK 7.

An example:

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

On windows i use Runtime.getRuntime().exec("cmd \\c ") and then use commandline rename function to actually rename files. It is much more flexible, e.g if you want to rename extension of all txt files in a dir to bak just write this to output stream:

rename *.txt *.bak

I know it is not a good solution but apparently it has always worked for me, much better then Java inline support.

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

The above is the simple code. I have tested on windows 7 and works perfectly fine.

Why not....

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

works on nwindows 7, does nothing if existingFile does not exist, but obviously could be better instrumented to fix this.

In my case, the error was in the path of the parent directory. Maybe a bug, I had to use the substring to get a correct path.

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

In my case it seemed to be a dead object within my own application, which kept a handle to that file. So that solution worked for me:

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

Advantage: it is pretty quick, as there is no Thread.sleep() with a specific hardcoded time.

Disadvantage: that limit of 20 is some hardcoded number. In all my tests, i=1 is enough. But to be sure I left it at 20.

I had a similar issue. File was copied rather moving on Windows but worked well on Linux. I fixed the issue by closing the opened fileInputStream before calling renameTo(). Tested on Windows XP.

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

Well I have found a pretty straight forward solution to this problem -

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

As suggested by Argeman, you can place a counter and limit the number of times the while loop will run so that it doesn't get into an infinite loop in case of some file are being used by another windows process.

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