如何在 CSV 文件中查找指定数值模式并自动分类文件

本文介绍如何使用 java 遍历目录下的 csv 文件,逐行解析并检测是否同时包含特定数值(如 50、500、5000),满足条件则将文件移至 `invalid_files` 目录,否则归入 `processed` 目录。

在数据处理自动化场景中,常需根据 CSV 内容特征对文件进行智能分拣。例如:当某 CSV 的 score 列中同时出现 50、500 和 5000 这三个值时,视为“含异常数值模式”,应归类为无效文件;否则视为正常待处理文件。以下是一个结构清晰、可维护性强的 Java 实现方案,基于 OpenCSV 库(v5.7+)构建。

✅ 核心设计原则

  • 职责分离:目录扫描、文件解析、条件校验、文件移动各由独立方法承担;
  • 条件可配置:通过 Predicate 列表定义匹配逻辑,支持任意数量/类型的数值约束;
  • 高效终止:一旦确认文件含全部目标值(即触发“无效”判定),立即退出循环,避免冗余解析;
  • 健壮路径处理:自动创建目标子目录(processed / invalid_files),规避 NoSuchFileException。

? 示例 CSV 结构(以分号分隔)

car;score;description
Opel;30;43
Volvo;500;434
Kia;50;3
Toyota;4;4
Mazda;5000;4

对应实体类(注意 position = 1 表示第二列 score):

import com.opencsv.bean.CsvBindByPosition;
import lombok.Data;

@Data
public class CsvLine {
    @CsvBindByPosition(position = 1)
    private BigDecimal value; // 对应 score 列
}

⚙️ 完整可运行代码

import com.opencsv.bean.CsvToBeanBuilder;
import lombok.Data;

import java.io.*;
import java.math.BigDecimal;
import java.nio.file.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class CsvPatternClassifier {

    public static void main(String[] args) throws IOException {
        // 1. 配置目录与过滤器
        File directory = Path.of("/path/to/your/csv/directory").toFile();
        FilenameFilter csvFilter = (dir, name) -> name.toLowerCase().endsWith(".csv");

        // 2. 定义“非法值”条件:若某行 score == 50/500/5000 → 触发无效判定
        List
> invalidValueConditions = List.of(
                eq(new BigDecimal("50")),
                eq(new BigDecimal("500")),
                eq(new BigDecimal("5000"))
        );

        // 3. 启动批量处理
        processDirectory(directory, csvFilter, invalidValueConditions);
    }

    public static void processDirectory(File directory, FilenameFilter filter,
                                        List
> invalidConditions) throws IOException {
        Path processedDir = createDirectory(Path.of(directory.getAbsolutePath(), "processed"));
        Path invalidDir = createDirectory(Path.of(directory.getAbsolutePath(), "invalid_files"));

        File[] files = directory.listFiles(filter);
        if (files == null) return;

        for (File file : files) {
            boolean isValid = isFileValid(file, invalidConditions);
            Path targetDir = isValid ? processedDir : invalidDir;
            moveFile(file, targetDir, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    // 判定文件是否“有效”:即 —— 不同时包含所有非法值
    public static boolean isFileValid(File file, List
> invalidConditions) throws IOException {
        try (Reader reader = Files.newBufferedReader(file.toPath())) {
            List lines = new CsvToBeanBuilder(reader)
                    .withType(CsvLine.class)
                    .withSkipLines(1)     // 跳过表头
                    .withSeparator(';')   // 指定分隔符
                    .build()
                    .parse();

            Set foundInvalids = new HashSet<>();
            for (CsvLine line : lines) {
                if (line.getValue() == null) continue;
                // 检查当前值是否命中任一非法条件
                for (Predicate cond : invalidConditions) {
                    if (cond.test(line.getValue())) {
                        foundInvalids.add(line.getValue());
                        break;
                    }
                }
                // 一旦凑齐全部非法值,立即判定为无效文件
                if (foundInvalids.size() == invalidConditions.size()) {
          

return false; } } } return true; // 未集齐全部非法值 → 视为有效 } // 工具方法:生成“等于某值”的谓词 public static Predicate eq(BigDecimal target) { return value -> value != null && value.compareTo(target) == 0; } public static void moveFile(File file, Path targetDir, StandardCopyOption option) throws IOException { Files.move(file.toPath(), targetDir.resolve(file.getName()), option); } public static Path createDirectory(Path path) throws IOException { return Files.exists(path) ? path : Files.createDirectory(path); } }

⚠️ 关键注意事项

  • 分隔符适配:示例使用 ;,若你的 CSV 是逗号分隔,请将 .withSeparator(';') 改为 .withSeparator(',');
  • 编码声明:若 CSV 含中文或特殊字符,建议显式指定编码:
    Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)
  • 性能优化:对超大 CSV,可考虑流式解析(CsvParser)替代 CsvToBeanBuilder,避免全量加载内存;
  • 扩展性提示:如需支持“范围匹配”(如 score > 1000)或“正则匹配”,只需新增对应 Predicate 实现即可,无需修改主流程。

该方案彻底规避了原代码中因逻辑嵌套混乱、状态重置错误(如 found50 = false 位置不当)、资源重复关闭(br.close() 多次调用)导致的功能失效问题,兼具可读性、可测试性与生产就绪性。