如何读取带双表头的CSV数据?OpenCSV仅获第二表头值的问题解决
读取双表头CSV数据的解决方案
你的核心问题在于OpenCSV默认不支持双表头结构,且当前代码存在表头映射错误、实体类字段不匹配、CSV格式异常等问题,以下是针对性的解决方法:
问题根源分析
- 双表头结构未被处理:你的CSV是分组式双表头(第2行是分组标题如
OC(062),第3行是具体列名如Date/THHS5),而默认的HeaderColumnNameMappingStrategy仅支持单表头,直接跳过前两行会丢失Date等独立列的映射关系。 - 实体类错误:字段注解、变量名、toString输出存在拼写不一致(如
OC78字段对应toString里的OC62),且缺少必要的Getter/Setter方法,导致反射映射失败。 - 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(); } } }
额外注意事项
- 修复CSV格式:确保所有列名和数据在同一行,避免
IC S这类列名被拆分换行,否则会导致列数不匹配。 - 依赖检查:确保项目中引入了
opencsv和commons-lang3依赖(自定义策略需要用到StringUtils)。
内容的提问来源于stack exchange,提问作者Micr0
相关产品推荐
相关产品推荐

