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

Quartz作业读取CSV后移动文件报错:文件被占用如何解决?

问题:Quartz作业处理CSV文件后移动时提示文件被占用

问题描述

我要实现一个Quartz作业,功能是读取指定目录下的.csv文件,处理完成后将其移动到子目录。编写代码后执行时出现文件被占用的异常,即使改用try-with-resources结构后问题仍未解决,请问如何释放文件并完成移动?

初始代码

@Override
public void execute(JobExecutionContext context) {

    File directoryPath = new File("C:\\csv\\nov");
    // 在源目录下创建名为"processed"的子文件夹
    try {
        Files.createDirectory(Path.of(directoryPath.getAbsolutePath() + "/processed"));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    FilenameFilter textFileFilter = (dir, name) -> {
        String lowercaseName = name.toLowerCase();
        if (lowercaseName.endsWith(".csv")) {
            return true;
        } else {
            return false;
        }
    };
    // 获取所有CSV文件列表
    File filesList[] = directoryPath.listFiles(textFileFilter);
    System.out.println("指定目录下的文本文件列表:");
    for(File file : filesList) {

        try {
            List<CsvLine> beans = new CsvToBeanBuilder(new FileReader(file.getAbsolutePath(), StandardCharsets.UTF_16))
                    .....
                    .build()
                    .parse();

            for(CsvLine item: beans){

                    ....... sql查询操作

                    Optional<ProcessedWords> isFound = processedWordsService.findByKeyword(item.getKeyword());

                    ......................................
            }

        } catch (Exception e){
            e.printStackTrace();
        }

        // 文件处理完成后移动到新子目录
        Path copied = Paths.get(file.getAbsolutePath() + "/processed");
        Path originalPath = file.toPath();
        try {
            Files.move(originalPath, copied, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

执行异常信息

org.quartz.SchedulerException: Job threw an unhandled exception.
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-2.3.2.jar:na]
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) ~[quartz-2.3.2.jar:na]
Caused by: java.lang.RuntimeException: java.nio.file.FileSystemException: C:\csv\nov\07_06_26.csv -> C:\csv\nov\07_06_26.csv\processed: The process cannot access the file because it is being used by another process
    at com.wordscore.engine.processor.ImportCsvFilePostJob.execute(ImportCsvFilePostJob.java:114) ~[main/:na]
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.3.2.jar:na]
    ... 1 common frames omitted
Caused by: java.nio.file.FileSystemException: C:\csv\nov\07_06_26.csv -> C:\csv\nov\07_06_26.csv\processed: 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) ~[na:na]
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103) ~[na:na]

更新后的代码

@Override
public void execute(JobExecutionContext context) {

    File directoryPath = new File("C:\\csv\\nov");
    // 在源目录下创建名为"processed"的子文件夹
    try {
        Path path = Path.of(directoryPath.getAbsolutePath() + "/processed");
        if (!Files.exists(path) || !Files.isDirectory(path)) {
            Files.createDirectory(path);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    FilenameFilter textFileFilter = (dir, name) -> {
        String lowercaseName = name.toLowerCase();
        if (lowercaseName.endsWith(".csv")) {
            return true;
        } else {
            return false;
        }
    };
    // 获取所有CSV文件列表
    File filesList[] = directoryPath.listFiles(textFileFilter);
    System.out.println("指定目录下的文本文件列表:");
    for(File file : filesList) {

        try {
            try (var br = new FileReader(file.getAbsolutePath(), StandardCharsets.UTF_16)){
                List<CsvLine> beans = new CsvToBeanBuilder(br)
                        ......
                        .build()
                        .parse();

            for (CsvLine item : beans) {

                .....
                if (isFound.isPresent()) {
                    .........
        }}

        } catch (Exception e){
            e.printStackTrace();
        }

        // 文件处理完成后移动到新子目录
        Path copied = Paths.get(file.getAbsolutePath() + "/processed");
        Path originalPath = file.toPath();
        try {
            Files.move(originalPath, copied, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

解决方案

1. 修正文件移动的目标路径错误

你当前的移动路径逻辑完全错误:

Path copied = Paths.get(file.getAbsolutePath() + "/processed");

这段代码会生成类似C:\csv\nov\07_06_26.csv\processed的路径,相当于要把文件移动到以原文件名命名的目录下的processed子目录,这不符合需求,同时也会导致系统尝试创建嵌套路径时出错。

正确的目标路径应该是原目录下的processed子目录,保留原文件名:

// 目标路径:原目录/processed/原文件名
Path targetPath = Paths.get(directoryPath.getAbsolutePath(), "processed", file.getName());

2. 确保CSV解析器完全释放文件资源

虽然你用了try-with-resources包裹FileReader,但建议改用NIO的Files.newBufferedReader,它的资源管理更可靠,同时要确保CsvToBean解析完成后没有持有流的引用:

try (var br = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_16)) {
    List<CsvLine> beans = new CsvToBeanBuilder(br)
            .withType(CsvLine.class) // 替换为你的实体类
            // 可添加其他配置,比如分隔符、跳过表头等
            .build()
            .parse();
    // 处理CSV数据的逻辑
} catch (Exception e) {
    e.printStackTrace();
}

3. 检查数据库操作是否持有文件引用

处理CSV行时的数据库操作(比如processedWordsService.findByKeyword)如果不小心传入了文件相关的流或对象,可能导致资源未释放。确保数据库操作只处理数据,不持有任何文件资源。

4. 完整修正后的代码示例

@Override
public void execute(JobExecutionContext context) {
    File directoryPath = new File("C:\\csv\\nov");
    Path processedDir = Path.of(directoryPath.getAbsolutePath(), "processed");
    
    // 创建processed目录(仅当不存在时)
    try {
        if (!Files.exists(processedDir)) {
            Files.createDirectory(processedDir);
        }
    } catch (IOException e) {
        throw new RuntimeException("创建processed目录失败", e);
    }

    // 过滤CSV文件
    FilenameFilter csvFilter = (dir, name) -> name.toLowerCase().endsWith(".csv");
    File[] filesList = directoryPath.listFiles(csvFilter);
    if (filesList == null) {
        System.out.println("指定目录下无CSV文件或目录不存在");
        return;
    }

    System.out.println("待处理的CSV文件列表:");
    for (File file : filesList) {
        System.out.println(file.getName());
        
        // 处理CSV文件
        try (var br = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_16)) {
            List<CsvLine> beans = new CsvToBeanBuilder(br)
                    .withType(CsvLine.class)
                    // 添加你的配置,比如.withSeparator(',')等
                    .build()
                    .parse();

            for (CsvLine item : beans) {
                // 执行数据库操作逻辑
                Optional<ProcessedWords> isFound = processedWordsService.findByKeyword(item.getKeyword());
                // ... 其他业务处理
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 处理失败时可选择跳过移动,或标记为错误文件
            continue;
        }

        // 移动文件到processed目录
        Path originalPath = file.toPath();
        Path targetPath = processedDir.resolve(file.getName());
        try {
            Files.move(originalPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("已移动文件: " + file.getName());
        } catch (IOException e) {
            throw new RuntimeException("移动文件失败: " + file.getName(), e);
        }
    }
}

5. 额外排查点

  • 检查是否有其他进程(如文本编辑器、杀毒软件)正在访问这些CSV文件
  • Windows系统中,确保文件流完全关闭后再执行移动操作(try-with-resources会自动关闭,但需确认无隐式引用)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 20:50:30