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

如何读取带双表头的CSV数据?OpenCSV仅获第二表头值的问题解决

读取双表头CSV数据的解决方案

你的核心问题在于OpenCSV默认不支持双表头结构,且当前代码存在表头映射错误、实体类字段不匹配、CSV格式异常等问题,以下是针对性的解决方法:

问题根源分析

  1. 双表头结构未被处理:你的CSV是分组式双表头(第2行是分组标题如OC(062),第3行是具体列名如Date/THHS5),而默认的HeaderColumnNameMappingStrategy仅支持单表头,直接跳过前两行会丢失Date等独立列的映射关系。
  2. 实体类错误:字段注解、变量名、toString输出存在拼写不一致(如OC78字段对应toString里的OC62),且缺少必要的Getter/Setter方法,导致反射映射失败。
  3. CSV格式异常:部分列名(如IC S)被拆分为两行,导致列数不匹配,字段映射错位。

方案一:自定义双表头映射策略(推荐)

通过自定义MappingStrategy合并双表头,实现自动映射:

1. 自定义映射策略类

import org.apache.commons.lang3.StringUtils;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DualHeaderMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
    private Map<String, Integer> columnIndexMap;

    public void setDualHeaders(List<String> firstHeader, List<String> secondHeader) {
        columnIndexMap = new HashMap<>();
        for (int i = 0; i < secondHeader.size(); i++) {
            String group = firstHeader.get(i);
            String column = secondHeader.get(i);
            // 合并表头:分组非空则用"分组-列名",否则直接用列名
            String fullColumnName = StringUtils.isNotBlank(group) ? group + "-" + column : column;
            columnIndexMap.put(fullColumnName.trim(), i);
        }
    }

    @Override
    public Integer getColumnIndex(String name) {
        return columnIndexMap.get(name);
    }
}

2. 修正实体类(匹配合并后的表头)

确保字段注解、变量名、Getter/Setter完全一致:

import com.opencsv.bean.CsvBindByName;

public class TemperatureData {
    @CsvBindByName(column = "Date")
    private String date;

    @CsvBindByName(column = "Time")
    private String time;

    @CsvBindByName(column = "Counter")
    private String counter;

    @CsvBindByName(column = "OC(062)-THHS5")
    private String OC62;

    @CsvBindByName(column = "IC(012)-IC S")
    private String IC;

    @CsvBindByName(column = "OC(062)-TH1")
    private String _TH1;

    @CsvBindByName(column = "OC(062)-TH2")
    private String _TH2;

    @CsvBindByName(column = "OC(062)-TH3")
    private String _TH3;

    @CsvBindByName(column = "IC(012)-IC S")
    private String _ICS;

    // 必须添加Getter/Setter
    public String getDate() { return date; }
    public void setDate(String date) { this.date = date; }

    public String getTime() { return time; }
    public void setTime(String time) { this.time = time; }

    public String getCounter() { return counter; }
    public void setCounter(String counter) { this.counter = counter; }

    public String getOC62() { return OC62; }
    public void setOC62(String OC62) { this.OC62 = OC62; }

    public String getIC() { return IC; }
    public void setIC(String IC) { this.IC = IC; }

    public String get_TH1() { return _TH1; }
    public void set_TH1(String _TH1) { this._TH1 = _TH1; }

    public String get_TH2() { return _TH2; }
    public void set_TH2(String _TH2) { this._TH2 = _TH2; }

    public String get_TH3() { return _TH3; }
    public void set_TH3(String _TH3) { this._TH3 = _TH3; }

    public String get_ICS() { return _ICS; }
    public void set_ICS(String _ICS) { this._ICS = _ICS; }

    @Override
    public String toString() {
        return "CsvData{" +
                "date='" + date + '\'' +
                ", time='" + time + '\'' +
                ", counter='" + counter + '\'' +
                ", OC='" + OC62 + '\'' +
                ", IC='" + IC + '\'' +
                ", TH1='" + _TH1 + '\'' +
                ", TH2='" + _TH2 + '\'' +
                ", TH3='" + _TH3 + '\'' +
                ", ICS='" + _ICS + '\'' +
                '}';
    }
}

3. 修改读取代码

import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import com.opencsv.CSVParserBuilder;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import java.io.InputStreamReader;
import java.util.*;
import java.io.IOException;

public class CsvReaderDemo {
    public static void main(String[] args) {
        // 替换为你的文件输入流
        try (CSVReader csvReader = new CSVReaderBuilder(new InputStreamReader(file.getInputStream()))
                .withCSVParser(new CSVParserBuilder().withSeparator(',').build())
                .build()) {

            // 手动读取前两行表头
            List<String> firstHeader = Arrays.asList(csvReader.readNext());
            List<String> secondHeader = Arrays.asList(csvReader.readNext());

            // 初始化自定义双表头策略
            DualHeaderMappingStrategy<TemperatureData> strategy = new DualHeaderMappingStrategy<>();
            strategy.setType(TemperatureData.class);
            strategy.setDualHeaders(firstHeader, secondHeader);

            // 构建CsvToBean,无需跳过行(已手动读取表头)
            CsvToBean<TemperatureData> csvToBean = new CsvToBeanBuilder<TemperatureData>(csvReader)
                    .withMappingStrategy(strategy)
                    .build();

            List<TemperatureData> dataList = csvToBean.parse();

            // 分组逻辑保持不变
            Map<String, List<TemperatureData>> groupedData = new HashMap<>();
            for (TemperatureData data : dataList) {
                String key = data.getOC62();
                groupedData.computeIfAbsent(key, k -> new ArrayList<>()).add(data);
            }

            // 输出测试
            for (Map.Entry<String, List<TemperatureData>> entry : groupedData.entrySet()) {
                System.out.println("Key: " + entry.getKey());
                entry.getValue().forEach(System.out::println);
                System.out.println("------------------------------");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

方案二:手动解析每行数据(简单直接)

如果自定义策略过于复杂,可直接遍历每行数据,根据双表头索引手动映射:

import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import com.opencsv.CSVParserBuilder;
import java.io.InputStreamReader;
import java.util.*;
import java.io.IOException;

public class ManualCsvReader {
    public static void main(String[] args) {
        try (CSVReader csvReader = new CSVReaderBuilder(new InputStreamReader(file.getInputStream()))
                .withCSVParser(new CSVParserBuilder().withSeparator(',').build())
                .build()) {

            String[] firstHeader = csvReader.readNext();
            String[] secondHeader = csvReader.readNext();
            List<TemperatureData> dataList = new ArrayList<>();

            String[] row;
            while ((row = csvReader.readNext()) != null) {
                TemperatureData data = new TemperatureData();
                for (int i = 0; i < row.length; i++) {
                    String group = firstHeader[i];
                    String column = secondHeader[i];
                    String value = row[i].trim();

                    switch (column) {
                        case "Date":
                            data.setDate(value);
                            break;
                        case "Time":
                            data.setTime(value);
                            break;
                        case "Counter":
                            data.setCounter(value);
                            break;
                        case "THHS5":
                            if ("OC(062)".equals(group)) data.setOC62(value);
                            break;
                        case "IC S":
                            if ("IC(012)".equals(group)) {
                                data.setIC(value);
                                data.set_ICS(value);
                            }
                            break;
                        case "TH1":
                            if ("OC(062)".equals(group)) data.set_TH1(value);
                            break;
                        case "TH2":
                            if ("OC(062)".equals(group)) data.set_TH2(value);
                            break;
                        case "TH3":
                            if ("OC(062)".equals(group)) data.set_TH3(value);
                            break;
                    }
                }
                dataList.add(data);
            }

            // 分组输出逻辑同方案一
            Map<String, List<TemperatureData>> groupedData = new HashMap<>();
            for (TemperatureData data : dataList) {
                String key = data.getOC62();
                groupedData.computeIfAbsent(key, k -> new ArrayList<>()).add(data);
            }

            for (Map.Entry<String, List<TemperatureData>> entry : groupedData.entrySet()) {
                System.out.println("Key: " + entry.getKey());
                entry.getValue().forEach(System.out::println);
                System.out.println("------------------------------");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

额外注意事项

  1. 修复CSV格式:确保所有列名和数据在同一行,避免IC S这类列名被拆分换行,否则会导致列数不匹配。
  2. 依赖检查:确保项目中引入了opencsv和commons-lang3依赖(自定义策略需要用到StringUtils)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 03:28:15