求助:如何读取含不同表头及列数的多工作表Excel文件(不可改原文件)
解决方案:根据表头动态映射列到对象字段
你的核心问题是现有代码固定了列索引来赋值Employee对象,但不同工作表的表头和列位置不一致,所以我们需要先读取每个工作表的表头,建立「表头名称→列索引」的映射,再根据这个映射动态提取数据,完全不用修改原Excel文件。
具体实现思路
- 预定义映射规则:把所有可能对应
Employee字段的表头文本和字段名关联起来(比如"员工姓名"、"EmpName"都对应empName)。 - 读取每个工作表的表头,生成当前表的「表头文本→列索引」映射表。
- 遍历数据行时,通过映射表找到目标字段对应的列,提取值并赋值给
Employee对象,同时处理列缺失、单元格类型不一致的情况。
修改后的完整代码
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class ReadExcelFileAndStore { // 自定义表头与Employee字段的映射关系,根据你的实际表头补充所有可能的名称 private static final Map<String, String> COLUMN_MAPPING = new HashMap<>(); static { // 所有对应empName的表头文本 COLUMN_MAPPING.put("员工姓名", "empName"); COLUMN_MAPPING.put("EmpName", "empName"); COLUMN_MAPPING.put("姓名", "empName"); // 所有对应extCode的表头文本 COLUMN_MAPPING.put("外部编码", "extCode"); COLUMN_MAPPING.put("ExtCode", "extCode"); COLUMN_MAPPING.put("员工编码", "extCode"); } public List<Employee> getTheFileAsObject(String filePath){ List<Employee> employeeList = new ArrayList<>(); // 使用try-with-resources自动关闭流,避免资源泄漏 try (FileInputStream file = new FileInputStream(new File(filePath))) { HSSFWorkbook workbook = new HSSFWorkbook(file); int numberOfSheets = workbook.getNumberOfSheets(); for(int i = 0; i < numberOfSheets; i++) { HSSFSheet sheet = workbook.getSheetAt(i); Iterator<Row> rowIterator = sheet.rowIterator(); // 跳过空表 if (!rowIterator.hasNext()) { continue; } // 读取表头,构建当前表的表头索引映射 Row headerRow = rowIterator.next(); Map<String, Integer> headerIndexMap = new HashMap<>(); Iterator<Cell> headerCellIterator = headerRow.cellIterator(); while (headerCellIterator.hasNext()) { Cell cell = headerCellIterator.next(); String headerText = getCellContent(cell).trim(); if (!headerText.isEmpty()) { headerIndexMap.put(headerText, cell.getColumnIndex()); } } // 遍历数据行,动态填充Employee对象 while (rowIterator.hasNext()) { Row row = rowIterator.next(); Employee employee = new Employee(); // 填充empName字段 Integer nameColIndex = getTargetColumnIndex(headerIndexMap, "empName"); if (nameColIndex != null && row.getCell(nameColIndex) != null) { employee.setEmpName(getCellContent(row.getCell(nameColIndex))); } // 填充extCode字段,兼容数字和字符串类型的单元格 Integer codeColIndex = getTargetColumnIndex(headerIndexMap, "extCode"); if (codeColIndex != null && row.getCell(codeColIndex) != null) { Cell codeCell = row.getCell(codeColIndex); if (codeCell.getCellType() == CellType.NUMERIC) { employee.setExtCode((int) codeCell.getNumericCellValue()); } else if (codeCell.getCellType() == CellType.STRING) { try { employee.setExtCode(Integer.parseInt(codeCell.getStringCellValue())); } catch (NumberFormatException e) { // 处理非数字的编码值,这里可以根据需求设默认值或跳过 employee.setExtCode(null); } } } employeeList.add(employee); } } } catch (IOException e) { e.printStackTrace(); } return employeeList; } // 辅助方法:统一获取单元格内容,兼容不同类型的单元格 private String getCellContent(Cell cell) { if (cell == null) { return ""; } switch (cell.getCellType()) { case STRING: return cell.getStringCellValue(); case NUMERIC: return String.valueOf(cell.getNumericCellValue()); case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); default: return ""; } } // 辅助方法:根据目标字段,从表头映射中找到对应的列索引 private Integer getTargetColumnIndex(Map<String, Integer> headerIndexMap, String targetField) { for (Map.Entry<String, String> entry : COLUMN_MAPPING.entrySet()) { if (entry.getValue().equals(targetField) && headerIndexMap.containsKey(entry.getKey())) { return headerIndexMap.get(entry.getKey()); } } return null; // 找不到对应列时返回null } } // 假设你的Employee实体类如下(可根据实际结构调整) class Employee { private String empName; private Integer extCode; public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } public Integer getExtCode() { return extCode; } public void setExtCode(Integer extCode) { this.extCode = extCode; } }
关键细节说明
- 灵活的表头适配:你只需要在
COLUMN_MAPPING里补充所有可能的表头文本,不管4个工作表用了什么命名的表头,都能匹配到对应的Employee字段。 - 异常安全处理:代码处理了空表、缺失列、单元格类型不匹配的情况,不会因为某张表结构特殊就抛出异常中断执行。
- 资源自动管理:使用
try-with-resources语法自动关闭文件流,避免手动关闭遗漏导致的资源泄漏。
内容的提问来源于stack exchange,提问作者Parkavi
相关产品推荐
相关产品推荐

