慎用File.renameTo(File)--用common.io.FileUtils.copyFile(file1,file2)

 
以前我一直以为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:
终于明白咯。 
做个实验: 
Java代码 
  
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));  

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));  
结果: 
Java代码 
  
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  

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()方法,在虚拟机终止的时候,删除掉这个目录或文件。 
BTW:File是文件和目录路径名的抽象表示形式,所以有可能是目录,千万小心。 

12 楼 clican 2008-07-28   引用 
我还碰到过在大压力情况下在windows renameTo有一定概率失败的情况。
在linux操作系统上在不同盘符之间renameTo也会失败。典型的应用场景就是从本地硬盘renameTo到mount的硬盘上或文件系统上。
11 楼 xiaobai1023 2008-07-18   引用 
有时候要注意对文件的引用是否已经关闭了
10 楼 cmoaciopm 2008-01-22   引用 
jdk版本:1.5.0_09 
操作系统:WindowsXP sp2 
环境:C盘为NTFS分区,F盘为FAT32分区 
现象:1.将C盘的文件renameTo到F盘,返回成功且文件移动成功 
      2.将F盘的文件renameTo到C盘,返回成功且文件移动成功 
并没有出现楼主所说的现象,不知道是不是jdk版本的问题
9 楼 温柔一刀 2008-01-21   引用 
确实是这样的,曾经遇到过。copy文件尽量使用FileUtilsc.opyFileToDirectory(File,File) 
还有个问题是在使用webwork的fileUpload拦截器的时候尽量不要使用execAndWait拦截器,两个拦截器会开启两个线程,很可能会删掉还没有处理的临时上传文件文件而导致文件找不到。
8 楼 zhanjianhua 2008-01-10   引用 
File oldFile = new File(souceFile); 
File newFile = new File(souceFile.replace(".", "del.")); 
oldFile.renameTo(newFile); 
这样修改文件名,oldFile, newFile怎么关闭,没看到JDK中有这方面的方法? 
还有这种修改文件名,无限制的修改下去,并且修改不同的文件名,会报java.io.IOException: java.io.IOException: Too many open files的错不
7 楼 Jonney 2007-12-22   引用 
很久以前看过一本书介绍了java里面的很多个陷阱, 
从此我一直把java.io.File当成是java.io.Filename来看待。
6 楼 linxizeng 2007-12-21   引用 
就是喜欢这种实际经验的贴...
5 楼 xiaoych 2007-12-21   引用 
zhangzhaofeng 写道
对楼主说的进行了在Windows下面的测试 
我自己发现有3种情况会在 
不同盘之间的copy中出错 
第一:源文件不存在 
第二:目标文件存在 
第三:都不存在 
楼主的那种rename to d:\test1.txt => false 
我怀疑是第一种情况 
应该和盘符没有关系
我改正了一下逻辑错误,多谢你的提醒,呵呵
4 楼 xiaoych 2007-12-21   引用 
是的,File#renameTo(File)方法 有很多种情况会返回false或者抛出异常。我做的那个测试只是想表达其中的一种情况: 
就是源文件存在,目标文件不存在,对目标目录有写入权限(由于是windows下面的administrator,所以默认会有),但是"file systems"不同,也会返回失败。 
这是我们最需要注意的,也就是我写这个的原因。
3 楼 zhangzhaofeng 2007-12-21   引用 
可能我的判断是有问题的
rename好奇怪
2 楼 zhangzhaofeng 2007-12-21   引用 
对楼主说的进行了在Windows下面的测试
我自己发现有3种情况会在
不同盘之间的copy中出错
第一:源文件不存在
第二:目标文件存在
第三:都不存在
楼主的那种rename to d:\test1.txt => false 
我怀疑是第一种情况
应该和盘符没有关系
1 楼 myreligion 2007-12-19   引用 
如果目标文件已经存在,rename一般也会失败,返回false。这个东西还是很靠不住的,不知道实现者是怎么想的。


 

posted @ 2014-04-02 20:11  keia  阅读(3070)  评论(0编辑  收藏  举报