POI Excel 组样式设置
问题背景
在使用
SXSSFWorkbook
处理动态行数的Excel
时,遇到设置行组(groupRow
)不生效的问题。原因是行数超过SXSSFWorkbook
的内存窗口大小,导致已写的行被写入磁盘,内存中找不到对应行。解决方法包括设置窗口参数为-1
以保存所有行,或者先写入磁盘再用XSSFWorkbook
读取并设置行组。对于超大量数据,如10w+
行,建议采用后者避免内存压力。
实现原理
阅读Sheet
的groupRow
方法发现可以用反射实现,设置行对应的层级可实现组的效果
-
行层级控制:通过设置
SXSSFRow._outlineLevel
属性(行层级)实现分组折叠层级 -
大纲级别设置:使用
SXSSFSheet.setWorksheetOutlineLevelRowIfNecessary
设置工作表最大分组层级 -
内存优化:采用
SXSSFWorkbook
的流式处理,通过ROW_ACCESS_WINDOW_SIZE
控制内存行数 -
groupRow
方法源码如下
@Override
public void groupRow(int fromRow, int toRow) {
int maxLevelRow = -1;
for(SXSSFRow row : _rows.subMap(fromRow, toRow + 1).values()){
final int level = row.getOutlineLevel() + 1;
row.setOutlineLevel(level);
maxLevelRow = Math.max(maxLevelRow, level);
}
setWorksheetOutlineLevelRowIfNecessary((short) Math.min(Short.MAX_VALUE, maxLevelRow));
}
实现步骤
// 步骤1:反射初始化(只执行一次)
final Field outlineField = SXSSFRow.class.getDeclaredField("_outlineLevel");
outlineField.setAccessible(true);
final Method setOutlineMethod = SXSSFSheet.class.getDeclaredMethod("setWorksheetOutlineLevelRowIfNecessary", short.class);
setOutlineMethod.setAccessible(true);
// 步骤2:逐行处理数据
for (T data : list) {
// 步骤3:获取分组信息
QueryExportCellDTO cellInfo = getCellInfo(data); // 从数据中提取分组信息
// 步骤4:设置行层级
if (cellInfo != null) {
outlineField.setInt(row, cellInfo.getLevel()); // 设置_outlineLevel属性
maxLevelRow = Math.max(maxLevelRow, cellInfo.getLevel());
}
}
// 步骤5:设置工作表大纲级别
if (maxLevelRow > 0) {
setOutlineMethod.invoke(sheet, (short) maxLevelRow);
}
核心参数说明
参数 | 类型 | 作用 |
---|---|---|
_outlineLevel | int | 控制行分组层级(0为最外层,数值越大层级越深) |
setWorksheetOutline | Method | 设置工作表支持的最大折叠层级(超过该层级的折叠按钮将不会显示) |
QueryExportCellDTO | 自定义数据对象 | 包含level (层级)、groupFlag (是否分组行)、childrenSize (子节点数)等分组信息 |
注意事项
- 反射性能优化:反射对象
outlineField
和setOutlineMethod
在方法外初始化避免重复开销 - 层级范围:
Excel
支持0-7
级分组折叠,建议层级不要超过4级 - 内存控制:建议配合
SXSSFWorkbook(int rowAccessWindowSize)
构造函数限制内存行数 - 异常处理:反射调用后应恢复字段访问权限(当前代码未展示,可添加
finally
块处理)
效果示意图
▾ 层级1 (点击折叠)
▸ 层级2
▸ 层级3
▸ 层级3
▸ 层级2
▾ 层级1