Windows环境下Apache Commons FileUtils拷贝目录是否锁原文件?
Apache Commons FileUtils.copyDirectoryToDirectory() 在Windows下的文件锁定问题
该方法会锁定原文件吗?
答案是会。在Windows平台上,当这个方法拷贝单个文件时,会暂时锁定原文件,导致你的WRITER程序无法写入。
具体原因是:FileUtils默认用FileInputStream读取源文件,而Windows系统中,FileInputStream打开文件时采用的共享模式是FILE_SHARE_READ——简单说就是,其他进程可以读这个文件,但没法写、改或者删,直到这个输入流关闭(也就是当前文件拷贝完成)。如果WRITER刚好在某个文件拷贝期间尝试写入,大概率会遇到“文件已被占用”的错误。
怎么避免这个锁定问题?
要绕过这个限制,你需要自定义文件拷贝逻辑,让读取源文件时允许其他进程同时写入。这里有两个实用方案:
方案1:用Java NIO的FileChannel设置共享写权限
Java 7及以上版本支持针对Windows的扩展选项ExtendedOpenOption.FILE_SHARE_WRITE,可以在读取文件时明确允许其他进程写入。你可以写一个自定义的目录拷贝方法,替代FileUtils的默认实现:
import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.file.*; public class SafeDirectoryCopier { public static void copyDirectoryWithSharedAccess(Path sourceDir, Path targetDir) throws IOException { // 遍历源目录下的所有文件和子目录 Files.walk(sourceDir) .forEach(sourcePath -> { Path targetPath = targetDir.resolve(sourceDir.relativize(sourcePath)); try { if (Files.isDirectory(sourcePath)) { // 创建对应的目标目录 Files.createDirectories(targetPath); } else { // 打开源文件:允许共享读、共享写 try (FileChannel sourceChannel = FileChannel.open( sourcePath, StandardOpenOption.READ, ExtendedOpenOption.FILE_SHARE_READ, ExtendedOpenOption.FILE_SHARE_WRITE); // 打开目标文件:创建、写入、覆盖原有内容 FileChannel targetChannel = FileChannel.open( targetPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { // 高效传输文件内容 sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); } } } catch (IOException e) { throw new RuntimeException("拷贝文件失败: " + sourcePath, e); } }); } }
这个方法在读取源文件时不会阻止WRITER程序写入,完美解决锁定问题。
方案2:扩展FileUtils,自定义文件拷贝逻辑
如果你想继续用FileUtils的目录遍历、权限处理等现成功能,可以重写它的文件拷贝方法,用RandomAccessFile来读取源文件——它在Windows下的只读模式默认允许其他进程读写:
import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class SharedAccessFileUtils extends FileUtils { public static void copyFileWithSharedRead(File source, File destination) throws IOException { // 用RandomAccessFile打开源文件(只读模式)和目标文件(读写模式) try (RandomAccessFile rafSource = new RandomAccessFile(source, "r"); RandomAccessFile rafDest = new RandomAccessFile(destination, "rw")) { // 用IOUtils拷贝流内容 IOUtils.copy(rafSource.getInputStream(), rafDest.getOutputStream()); } } }
之后你可以遍历源目录的所有文件,调用这个自定义的copyFileWithSharedRead方法,完成整个目录的拷贝。
额外提醒
- 这些方案主要针对Windows系统,Linux等类Unix系统默认读取文件不会阻止写入,所以不需要额外处理。
- 允许共享写后,拷贝过程中可能会读到WRITER程序正在写入的部分内容,如果需要保证拷贝文件的完整性,可能需要额外的同步机制——比如让WRITER先写入临时文件,完成后再重命名到目标位置,或者用文件锁来协调拷贝和写入操作。
内容的提问来源于stack exchange,提问作者Flatfoot
相关产品推荐
相关产品推荐

