以前我一直以为File#renameTo(File)方法与OS下面的 move/mv 命令是相同的,可以达到改名、移动文件的目的。不过后来经常发现问题:File#renameTo(File)方法会返回失败(false),文件没有移动,又查不出原因,再后来干脆弃用该方法,自己实现一个copy方法,问题倒是再也没有出现过。
昨天老板同学又遇到这个问题,File#renameTo(File)方法在windows下面工作的好好的,在linux下偶尔又失灵了。回到家我扫了一遍JDK中File#renameTo(File)方法的源代码,发现它调用的是一个本地的方法(native method),无法再跟踪下去。网上有人说该方法在window下是正常的,在linux下面是不正常的。这个很难说通,SUN不可能搞出这种平台不一致的代码出来啊。
后面在SUN的官方论坛上看到有人提到这个问题“works on windows, don't work on linux”,后面有人回复说是“file systems”不一样。究竟怎么不一样呢?还是没有想出来...
后面在一个论坛里面发现了某人关于这个问题的阐述:
引用
In the Unix'esque O/S's you cannot renameTo() across file systems. This behavior is different than the Unix "mv" command.
When crossing file systems mv does a copy and delete which is what you'll have to do if this is the case.
The same thing would happen on Windows if you tried to renameTo a different drive, i.e. C: -> D:
File sourceFile = new File("c:/test.txt");
File targetFile1 = new File("e:/test.txt");
File targetFile2 = new File("d:/test.txt");
System.out.println("source file is exist? " + sourceFile.exists() + ", source file => " + sourceFile);
System.out.println(targetFile1 + " is exist? " + targetFile1.exists());
System.out.println("rename to " + targetFile1 + " => " + sourceFile.renameTo(targetFile1));
System.out.println("source file is exist? " + sourceFile.exists() + ", source file => " + sourceFile);
System.out.println(targetFile2 + " is exist? " + targetFile2.exists());
System.out.println("rename to " + targetFile2 + " => " + sourceFile.renameTo(targetFile2));
source file is exist? true, source file => c:\test.txt
e:\test.txt is exist? false
rename to e:\test.txt => false
source file is exist? true, source file => c:\test.txt
d:\test.txt is exist? false
rename to d:\test.txt => true
果然是不能把File#renameTo(File)当作move方法使用。
可以考虑使用apache组织的commons-io包里面的FileUtils#copyFile(File,File)和FileUtils#copyFileToDirectory(File,File)方法实现copy的效果。至于删除嘛,我想如果要求不是那么精确,可以调用File#deleteOnExit()方法,在虚拟机终止的时候,删除掉这个目录或文件。
OK,到此为止都是引用原作者的内容,http://xiaoych.iteye.com/blog/149328,一下是我自己做的实验。
首先在windows上作实验。
import java.io.File;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Test");
File sourceFile = new File("c:/test.txt");
File targetFile = new File("d:/test.txt");
try {
boolean result = targetFile.renameTo(sourceFile);
System.out.println("result = " + result);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
接着实验,我又换了一个环境,在Ubuntu上做实验。
package com.darren.test;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
public class Test {
public static void main(String[] args) {
File source = new File("/test.txt");
File target = new File("/dev/test.txt");
Test.testCopy(source, target);
}
public static void testRenameTo(File source, File target) {
try {
boolean result = source.renameTo(target);
System.out.println(result);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void testCopy(File source, File target) {
try {
FileUtils.copyFile(source, target);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
再做一个测试:
最近遇到一个BUG,说mount一个forder之后,使用renameTo方法失败,然后我就在Ubuntu上做了这个测试,发现确实失败,因为mount之后得到文件系统有变化,导致使用renameTo前后两个目录的文件系统不一致,导致无法移动,所以在此说明renameTo方法不等同于操作系统的move方法,当然可以使用apache的commons-io中的FileUtils.moveFile(source, target)去替换renameTo方法。那么还有没有官方的证据证明renameTo方法存在缺陷呢? 答案是当然有,让我门看一下JDK的API文档:
public boolean renameTo(File dest)
Renames the file denoted by this abstract pathname.
Many aspects of the behavior of this method are inherently platform-dependent: The rename operation might not be able to
move a file from one filesystem to another, it might not be atomic, and it might not succeed if a file with the destination
abstract pathname already exists. The return value should always be checked to make sure that the rename operation
was successful.
Note that the Files class defines the move method to move or rename a file in a platform independent manner.
Parameters:
dest - The new abstract pathname for the named file
Returns:
true if and only if the renaming succeeded; false otherwise
Throws:
SecurityException - If a security manager exists and its SecurityManager.checkWrite(java.lang.String) method denies write
access to either the old or new pathnames
NullPointerException - If parameter dest is null