0

我正在尝试解析一个几乎有效的 CSV 文件,其中包含 99.9% 正确和有效的数据。但是中途有几条记录无效(引号太多),例如

a,b,"c",d 
a,b,""c""",d

我的代码

    try (Reader reader = new BufferedReader(new FileReader(file), BUFFERED_READER_SIZE);
         CSVParser csvParser = new CSVParser(reader, CSVFormat.EXCEL)
    ) {
        Iterator<CSVRecord> iterator = csvParser.iterator();
        CSVRecord record;
        while (iterator.hasNext()) {
            try {
                record = iterator.next();
            } catch (IllegalStateException e) {
            }
        }
    } catch (IOException e) {
    }

如何解析 CSV,以便在遇到无效行/记录时跳过它并移至下一行?

4

1 回答 1

0

我不认为你可以做很多工作来解决它。CSVParser是一个最终类,并且不允许控制它解析底层流的方式。但是,可以通过使用自定义迭代器来解决这个问题

public final class Csv {

    private Csv() {
    }

    public interface ICsvParserFactory {

        @Nonnull
        CSVParser createCsvParser(@Nonnull Reader lineReader);

    }

    public static Stream<CSVRecord> tryParseLinesLeniently(final BufferedReader bufferedReader, final ICsvParserFactory csvParserFactory) {
        return bufferedReader.lines()
                .map(line -> {
                    try {
                        return csvParserFactory.createCsvParser(new StringReader(line))
                                .iterator()
                                .next();
                    } catch ( final IllegalStateException ex ) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .onClose(() -> {
                    try {
                        bufferedReader.close();
                    } catch ( final IOException ex ) {
                        throw new RuntimeException(ex);
                    }
                });
    }

}

但是,无论如何,我认为这不是一个好主意:

  • 它不能返回CSVParser实例。
  • 可能会返回一个Iterator<CSVRecord>而不是Stream<CSVRecord>(并保存filter操作),但我只是发现流更易于实现。
  • 它为每一行创建一个新的 CSV 解析器,因此此方法为包含“太多”行的 CSV 文档创建许多对象。字符串阅读器可能可以重复使用。
  • 该方法的整个想法是,它不是 CSV 解析器,它假设每行仅包含一行(我真的不记得 CSV/TSV 是否允许多行记录),因此它违反了 CSV 解析规则设计。它还不支持标题(但可以很容易地改进)。
final Csv.ICsvParserFactory csvParserFactory = lineReader -> {
    try {
        return new CSVParser(lineReader, CSVFormat.EXCEL);
    } catch ( final IOException ex ) {
        throw new RuntimeException(ex);
    }
};
try ( final Stream<CSVRecord> csvRecords = Csv.tryParseLinesLeniently(new BufferedReader(reader), csvParserFactory) ) {
    csvRecords.forEachOrdered(System.out::println);
}

如果可能,请让您的 CSV 解析器使用有效的 CSV 文档,而不是使用像这样的任何解决方法。


编辑 1

上面的代码中存在一个实现缺陷:从流返回的所有记录现在都recordNumber设置为1.

现在我确实相信无法使用 Apache Commons CSV 解析器修复请求,因为唯一的CSVRecord构造函数也是包私有的,如果不使用反射或侵入其声明包,则无法在该包之外实例化。

抱歉,您要么修复了 CSV 文档,要么使用了另一个可以“更宽松”解析的解析器。

于 2020-12-17T12:33:08.870 回答