apache commons csv忽略csv文件中损坏或无效的记录并继续解析

zour9fqk  于 2021-06-29  发布在  Java
关注(0)|答案(1)|浏览(456)

我试图解析一个几乎有效的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,使它在遇到无效行/记录时跳过它并移到下一行?

jei2mxaa

jei2mxaa1#

我觉得你没办法解决这个问题。 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 .
现在我相信这个请求不能用apachecommons csv解析器修复,因为 CSVRecord 构造函数也是包私有的,如果不使用反射或入侵其声明包,则不能在包外部示例化。
抱歉,你要么修复了你的csv文件,要么使用另一个解析器,可以解析“更宽松”。

相关问题