POI 读取 Excel 合并单元格内容:完整解决方案
在处理包含合并单元格的 Excel 时,直接读取非左上角的单元格会返回空值(因为合并单元格仅左上角单元格保存数据)。本文将详细讲解如何识别合并单元格、读取合并区域的内容,并提供通用工具类,确保在各种合并场景下准确获取数据。
合并单元格的特性与读取难点
合并单元格的存储规则
Excel 中合并单元格(如 CellRangeAddress
定义的区域)仅在左上角第一个单元格中存储数据,其他被合并的单元格均为空。例如:
- 合并
A1:A3
(3 行 1 列)后,仅 A1
有值,A2
、A3
为空;
- 合并
B2:D4
(3 行 3 列)后,仅 B2
有值,B3
、C2
等均为空。
直接读取的问题
若直接通过 row.getCell(column)
读取合并区域内的非左上角单元格,会返回 null
或空值,导致数据丢失。因此,需要:
- 先判断单元格是否属于合并区域;
- 若属于,则读取合并区域左上角单元格的值。
核心工具类实现
以下工具类提供合并单元格的判断、内容读取等功能,兼容 .xls
和 .xlsx
格式。
工具类代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress;
public class MergedCellReader {
public static boolean isMergedCell(Sheet sheet, int rowIndex, int colIndex) { int mergedRegionCount = sheet.getNumMergedRegions(); for (int i = 0; i < mergedRegionCount; i++) { CellRangeAddress region = sheet.getMergedRegion(i); boolean isInRowRange = rowIndex >= region.getFirstRow() && rowIndex <= region.getLastRow(); boolean isInColRange = colIndex >= region.getFirstColumn() && colIndex <= region.getLastColumn(); if (isInRowRange && isInColRange) { return true; } } return false; }
public static String getMergedCellValue(Sheet sheet, int rowIndex, int colIndex) { int mergedRegionCount = sheet.getNumMergedRegions(); for (int i = 0; i < mergedRegionCount; i++) { CellRangeAddress region = sheet.getMergedRegion(i); if (rowIndex >= region.getFirstRow() && rowIndex <= region.getLastRow() && colIndex >= region.getFirstColumn() && colIndex <= region.getLastColumn()) { Row firstRow = sheet.getRow(region.getFirstRow()); if (firstRow == null) { return ""; } Cell firstCell = firstRow.getCell(region.getFirstColumn(), Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); return getCellValue(firstCell); } } return null; }
private static String getCellValue(Cell cell) { if (cell == null) { return ""; } CellType cellType = cell.getCellType(); switch (cellType) { case STRING: return cell.getStringCellValue(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { return cell.getDateCellValue().toString(); } else { return String.valueOf(cell.getNumericCellValue()); } case BOOLEAN: return String.valueOf(cell.getBooleanCellValue()); case FORMULA: return getCellValue(cell.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator().evaluateInCell(cell)); default: return ""; } }
public static String readCellValue(Sheet sheet, int rowIndex, int colIndex) { Row row = sheet.getRow(rowIndex); if (row == null) { return ""; } if (isMergedCell(sheet, rowIndex, colIndex)) { return getMergedCellValue(sheet, rowIndex, colIndex); } else { Cell cell = row.getCell(colIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); return getCellValue(cell); } } }
|
工具类核心功能
isMergedCell
:判断单元格是否属于合并区域;
getMergedCellValue
:获取合并区域的内容(仅对合并单元格有效);
readCellValue
:通用读取方法,自动判断是否为合并单元格并返回正确内容;
getCellValue
:处理单元格的各种数据类型(字符串、数字、日期、公式等),统一转为字符串。
使用示例
以下示例读取包含合并单元格的 Excel,并打印所有单元格内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import org.apache.poi.ss.usermodel.*; import java.io.FileInputStream; import java.io.IOException;
public class ReadMergedCellDemo { public static void main(String[] args) { String filePath = "合并单元格示例.xlsx"; try (Workbook workbook = WorkbookFactory.create(new FileInputStream(filePath))) { Sheet sheet = workbook.getSheetAt(0); for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) { Row row = sheet.getRow(rowIndex); if (row == null) { continue; } int lastColIndex = row.getLastCellNum(); for (int colIndex = 0; colIndex < lastColIndex; colIndex++) { String value = MergedCellReader.readCellValue(sheet, rowIndex, colIndex); System.out.printf("行[%d], 列[%d]:%s\t", rowIndex, colIndex, value); } System.out.println(); } } catch (IOException e) { e.printStackTrace(); } } }
|
常见问题与优化技巧
1. 性能优化
- 问题:若工作表包含大量合并区域(如几百个),
isMergedCell
循环判断可能耗时;
- 优化:
- 缓存合并区域信息(如转为
Map
存储行范围与列范围的映射);
- 对于固定格式的 Excel,可提前记录合并区域位置,避免每次读取时遍历。
2. 合并区域嵌套问题
- 问题:Excel 支持合并区域嵌套(如大合并区域包含小合并区域);
- 解决:
CellRangeAddress
的遍历顺序不影响结果,工具类会优先匹配最精确的合并区域(因嵌套区域的范围更小)。
3. 空行或空单元格处理
- 问题:工作表中可能存在空行(
Row
为 null
)或空单元格;
- 解决:
- 使用
Row.MissingCellPolicy.CREATE_NULL_AS_BLANK
确保空单元格返回非 null
对象;
- 遍历行时跳过
null
行(或视为空行处理)。
4. 公式单元格处理
- 问题:合并区域的左上角单元格若为公式,直接读取会返回公式字符串(如
SUM(A1:A3)
);
- 解决:工具类中
getCellValue
方法通过 FormulaEvaluator
自动计算公式结果,返回最终值。
v1.3.10