不管在什么地方,什么时候,学习是快速提升自己的能力的一种体现!!!!!!!!!!!
以前我一直以为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
注意:,从C盘到E盘失败了,从C盘到D盘成功了。因为我的电脑C、D两个盘是NTFS格式的,而E盘是FAT32格式的。所以从C到E就是上面文章所说的"file systems"不一样。从C到D由于同是NTFS分区,所以不存在这个问题,当然就成功了。
果然是不能把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) {
-
- 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) {
-
- e.printStackTrace();
- }
- }
-
- }
结构为true,但是我的C盘的文件系统为FAT32,D盘为NTFS,并不能重现原作者的问题。此时看了原文章的一些评论内容,说可能与系统有关,也有人说可能与JDK版本有关,但是现在我也不能确定具体与什么有关。
接着实验,我又换了一个环境,在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) {
-
- e.printStackTrace();
- }
- }
-
- public static void testCopy(File source, File target) {
- try {
- FileUtils.copyFile(source, target);
- } catch (IOException e) {
-
- e.printStackTrace();
- }
- }
- }
测试renameTo方法的时候失败了,而测试copy方法的时候成功了,我测试的两个目录的文件系统也不一样。
再做一个测试:
最近遇到一个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
所以说,当遇到多文件系统的时候,请尽量避免使用File#renameTo(File)方法,可是使用apache的commons-io包去替换,避免一些潜在BUG。