【问题标题】:How to read in CSV columns in any order using CSVParser from apache.commons如何使用 apache.commons 中的 CSVParser 以任何顺序读取 CSV 列
【发布时间】:2020-11-04 21:32:14
【问题描述】:

我有一个 csv 文件,其中包含一些这种格式的数据:

id,first,last,city
1,john,doe,austin
2,jane,mary,seattle

到目前为止,我正在使用以下代码读取 csv:

    String path = "./data/data.csv";
    Map<Integer, User> map = new HashMap<>();

    Reader reader = Files.newBufferedReader(Paths.get(path));

    try (CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT)) {

        List<CSVRecord> csvRecords = csvParser.getRecords();

        for(int i=0; i < csvRecords.size(); i++){

            if(0<i){//skip over header
                CSVRecord csvRecord = csvRecords.get(i);
                User currentUser = new User(
                        Double.valueOf(csvRecord.get(0)).intValue(),
                        Double.valueOf(csvRecord.get(1)),
                        Double.valueOf(csvRecord.get(2)),
                        Double.valueOf(csvRecord.get(3))
                );
                map.put(currentUser.getId(), currentUser);
            }
        }
    } catch (IOException e){
        System.out.println(e);
    }

获取正确的值,但如果这些值的顺序不同,比如 [city,last,id,first],它将被错误地读取,因为读取是使用顺序 [id,first,last] 硬编码的,城市]。 (用户对象也必须按照 id、first、last、city 的确切顺序创建字段)

我知道我可以使用 'withHeader' 选项,但这也需要我提前定义标题列顺序,如下所示:

String header = "id,first,last,city";
CSVParser csvParser = new CSVParser(reader, CSVFormat.EXCEL.withHeader(header.split(",")));

我也知道有一个built in function getHeaderNames(),但只有在我已经将它们作为字符串传入之后才会获取标题(再次进行硬编码)。因此,如果我传入标题字符串“last,first,id,city”,它将在列表中完全返回。

有没有办法将这些位组合起来以读取 csv,无论列顺序是什么,并使用按顺序传递的字段(id、first、last、city)定义我的“用户”对象?

【问题讨论】:

    标签: java csv parsing apache-commons


    【解决方案1】:

    我们需要告诉解析器为我们处理标题行。我们将其指定为CSVFormat 的一部分,因此我们将创建如下自定义格式:

    CSVFormat csvFormat = CSVFormat.RFC4180.withFirstRecordAsHeader();
    

    使用了DEFAULT 的问题代码,但这是基于RFC4180 而不是。并排比较它们:

    DEFAULT                               RFC4180                       Comment
    ===================================   ===========================   ========================
    withDelimiter(',')                    withDelimiter(',')            Same
    withQuote('"')                        withQuote('"')                Same
    withRecordSeparator("\r\n")           withRecordSeparator("\r\n")   Same
    withIgnoreEmptyLines(true)            withIgnoreEmptyLines(false)   Don't ignore blank lines
    withAllowDuplicateHeaderNames(true)   -                             Don't allow duplicates
    ===================================   ===========================   ========================
                                          withFirstRecordAsHeader()     We need this
    

    有了这个改变,我们可以调用get(String name)而不是get(int i)

    User currentUser = new User(
            Integer.parseInt(csvRecord.get("id")),
            csvRecord.get("first"),
            csvRecord.get("last"),
            csvRecord.get("city")
    );
    

    注意CSVParser实现了Iterable&lt;CSVRecord&gt;,所以我们可以使用for-each循环,这使得代码看起来像这样:

    String path = "./data/data.csv";
    
    Map<Integer, User> map = new HashMap<>();
    try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(Paths.get(path)),
                                             CSVFormat.RFC4180.withFirstRecordAsHeader())) {
        for (CSVRecord csvRecord : csvParser) {
            User currentUser = new User(
                    Integer.parseInt(csvRecord.get("id")),
                    csvRecord.get("first"),
                    csvRecord.get("last"),
                    csvRecord.get("city")
            );
            map.put(currentUser.getId(), currentUser);
        }
    }
    

    即使列顺序发生变化,该代码也会正确解析文件,例如到:

    last,first,id,city
    doe,john,1,austin
    mary,jane,2,seattle
    

    【讨论】:

    • 谢谢!你能解释一下为什么我们需要 RCF4180 而不是 DEFAULT 吗?我使用您发布的代码尝试了两种格式,它似乎也可以使用 DEFEAULT,除非我错过了其他东西
    • @reeeeeeeeeeee 请再次阅读答案。我添加了DEFAULTRCF4180 的比较是有原因的。我什至添加了 cmets 来解释差异。
    • 是的,我确实读过,但我也插入了 DEFAULT 而不是 RCF4180,发现它确实有效并且 withFirstRecordAsHeader() 可用
    • @reeeeeeeeeeee 当然可以,因为您的数据没有空行或重复的标题名称。你明白这些是什么意思,对吧?问题是,哪种格式更好,以防这些情况确实出现在数据中。在发生意外情况时使代码做出适当的反应称为defensive programming,这是合并到您的代码中的一件好事。忽略错误处理会给你以后带来很多麻烦。
    • 知道了。顺便说一句,打印 DEFAULT.getAllowDuplicateHeaderNames() 和 RCF 相同...都返回 true。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 1970-01-01
    • 2012-06-11
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    相关资源
    最近更新 更多