You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Java walkFileTree和SimpleFileVisitor删文件遇FileSystemException问题排查

问题

使用Java的walkFileTree结合SimpleFileVisitor删除文件时,同一目录下8个文件中有2个无法删除,报错提示文件被其他进程占用。相关代码及错误日志如下:

代码示例

public class ExtraFileCleaner implements AutoCloseable {
    
    private static final ExecutorService EXE_SERVICE = Executors.newFixedThreadPool(1);
    
    protected ExtraFileCleaner() {
        //TODO
    }

    public void deleteFilePath(final Path path) {
        if (path != null && Files.exists(path) && Files.isWritable(path)) {
            EXE_SERVICE.submit(() -> {
                try {
                    Files.walkFileTree(path, new DeletingFileVistor());
                } catch (NoSuchFileException e) {
                    log.debug("Path does not exist: " + path);
                } catch (IOException e) {
                    log.warn("Delete failed: {} ", e.getMessage());
                }
            });
        }
    }  

    @Override
    public void close() throws InterruptedException {
        DELETE_SERVICE.shutdown();
        if (!EXE_SERVICE.awaitTermination(1 * 60 * 1000, TimeUnit.MILLISECONDS)) {
            EXE_SERVICE.shutdownNow();
        }       
    }
    
    public static class DeletingFileVistor extends SimpleFileVisitor<Path> {
                                
        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
            log.info("visitFile: {}", path.toString());
            return visit(path);
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException {
            log.info("POSTvisitFile: {}", path.toString());
            return visit(path);
        }
        
        private FileVisitResult visit(Path path) {
            FibonacciDelay delay = new FibonacciDelay();
            for (int attempt = 0; attempt < delay.getMaxAttempts(); attempt++) {
                try {
                    Files.deleteIfExists(path);
                    log.info("Deleted: {}", path.toString());
                    break;
                } catch (IOException e) {
                    String msg = e.getMessage();
                    
                    log.info("exception-2 {}", msg);
                    if (e instanceof FileSystemException && msg != null && msg.contains("The process cannot access the file because it is being used by another process")) {
                        log.debug("File is in use: {}", path.toString());
                    } else {
                        log.debug("{}: {}", new Object[]{e.getClass().getSimpleName(), e.getMessage()});
                    }
                    delay.doWait(attempt);   
                }
                if (attempt == delay.getMaxAttempts() - 1) {
                    log.warn("Delete aborted: {}", path.toString());
                }
            }
            return FileVisitResult.CONTINUE;
        }
    }
}

错误日志

java.nio.file.FileSystemException: C:\ProgramData\XYZ\FileManager\local\work\2b5f439e-cb80-4be9-a4ff-c7c796ba\test.1.0.jar: The process cannot access the file because it is being used by another process.

    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:274)
    at java.base/sun.nio.fs.AbstractFileSystemProvider.deleteIfExists(AbstractFileSystemProvider.java:110)
    at java.base/java.nio.file.Files.deleteIfExists(Files.java:1181)
    at com.zyz.abc.file.WorkDirectoryCleaner$DeletingFileVistor.visit(WorkDirectoryCleaner.java:110)
    at com.zyz.abc.file.WorkDirectoryCleaner$DeletingFileVistor.visitFile(WorkDirectoryCleaner.java:98)
    at com.zyz.abc.file.WorkDirectoryCleaner$DeletingFileVistor.visitFile(WorkDirectoryCleaner.java:94)
    at java.base/java.nio.file.Files.walkFileTree(Files.java:2725)
    at java.base/java.nio.file.Files.walkFileTree(Files.java:2797)
    at com.zyz.abc.file.WorkDirectoryCleaner.lambda$deletePath$0(WorkDirectoryCleaner.java:40)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
分析与解决方案

代码存在以下几处遗漏和优化点:

1. 遍历过程中文件句柄未及时释放

Files.walkFileTree遍历文件时会打开文件句柄读取属性,Windows系统下未释放的句柄会阻止文件删除。需在删除前尝试关闭可能残留的句柄:

@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
    log.info("visitFile: {}", path.toString());
    // 尝试关闭可能占用的文件句柄
    try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
        channel.close();
    } catch (IOException ignored) {
        // 忽略关闭失败,继续执行删除逻辑
    }
    return visit(path);
}

2. 线程池关闭逻辑的变量错误

close()方法中使用了未定义的DELETE_SERVICE,需改为EXE_SERVICE,否则线程池无法正常关闭,可能残留线程占用资源:

@Override
public void close() throws InterruptedException {
    EXE_SERVICE.shutdown(); // 修正变量名
    if (!EXE_SERVICE.awaitTermination(1 * 60 * 1000, TimeUnit.MILLISECONDS)) {
        EXE_SERVICE.shutdownNow();
    }       
}

3. 重试逻辑的针对性不足

当前重试逻辑未针对“文件被占用”场景做特殊处理:

  • 可延长斐波那契延迟的总等待时间,给其他进程足够的释放时间
  • 增加日志级别,明确标记被占用文件的重试状态

4. 目录删除的依赖检查缺失

postVisitDirectory中直接删除目录,若目录内有未删除成功的文件,删除会失败。需先检查目录是否为空:

@Override
public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException {
    log.info("POSTvisitFile: {}", path.toString());
    // 检查目录为空后再删除
    if (Files.list(path).findAny().isEmpty()) {
        return visit(path);
    } else {
        log.warn("Directory not empty, skip delete: {}", path);
        return FileVisitResult.CONTINUE;
    }
}

5. 权限检查时机不合理

deleteFilePath提前检查Files.isWritable(path),但提交到线程池执行时权限可能已变化,需将权限检查移至实际删除逻辑中,避免出现“检查时可写、删除时不可写”的矛盾情况。

内容的提问来源于stack exchange,提问作者JDGuide

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 22:41:59