0%

POI读取合并单元格内容

POI 读取 Excel 合并单元格内容:完整解决方案

在处理包含合并单元格的 Excel 时,直接读取非左上角的单元格会返回空值(因为合并单元格仅左上角单元格保存数据)。本文将详细讲解如何识别合并单元格、读取合并区域的内容,并提供通用工具类,确保在各种合并场景下准确获取数据。

合并单元格的特性与读取难点

合并单元格的存储规则

Excel 中合并单元格(如 CellRangeAddress 定义的区域)仅在左上角第一个单元格中存储数据,其他被合并的单元格均为空。例如:

  • 合并 A1:A3(3 行 1 列)后,仅 A1 有值,A2A3 为空;
  • 合并 B2:D4(3 行 3 列)后,仅 B2 有值,B3C2 等均为空。

直接读取的问题

若直接通过 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 {

/**
* 判断单元格是否属于合并区域
* @param sheet 工作表
* @param rowIndex 行索引(0 开始)
* @param colIndex 列索引(0 开始)
* @return 是合并区域返回 true,否则 false
*/
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;
}

/**
* 读取合并区域的内容(返回左上角单元格的值)
* @param sheet 工作表
* @param rowIndex 行索引
* @param colIndex 列索引
* @return 合并区域的内容,非合并区域返回 null
*/
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; // 非合并区域
}

/**
* 将单元格值转换为字符串(兼容各种数据类型)
* @param cell 单元格
* @return 单元格内容的字符串形式
*/
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 "";
}
}

/**
* 读取单元格内容(自动处理合并区域)
* @param sheet 工作表
* @param rowIndex 行索引
* @param colIndex 列索引
* @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);
// 遍历所有行(从第 0 行到最后一行)
for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
continue;
}
// 遍历当前行的所有列(从第 0 列到最大列)
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. 空行或空单元格处理

  • 问题:工作表中可能存在空行(Rownull)或空单元格;
  • 解决
    • 使用 Row.MissingCellPolicy.CREATE_NULL_AS_BLANK 确保空单元格返回非 null 对象;
    • 遍历行时跳过 null 行(或视为空行处理)。

4. 公式单元格处理

  • 问题:合并区域的左上角单元格若为公式,直接读取会返回公式字符串(如 SUM(A1:A3));
  • 解决:工具类中 getCellValue 方法通过 FormulaEvaluator 自动计算公式结果,返回最终值。

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10