POI Excel单元格样式超过最大数(4000或64000)的解决方案
问题现象
使用Apache POI生成Excel时,如果创建的单元格样式过多,会报样式超出最大数的错误,
.xls
的异常错误:
java.lang.IllegalStateException: The maximum number of cell styles was exceeded. You can define up to 4000 styles in a .xls workbook
at org.apache.poi.hssf.usermodel.HSSFWorkbook.createCellStyle(HSSFWorkbook.java:1144)
at org.apache.poi.hssf.usermodel.HSSFWorkbook.createCellStyle(HSSFWorkbook.java:88)
.xlsx
的异常错误:
java.lang.IllegalStateException: The maximum number of Cell Styles was exceeded. You can define up to 64000 style in a .xlsx Workbook
at org.apache.poi.xssf.model.StylesTable.createCellStyle(StylesTable.java:830)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.createCellStyle(XSSFWorkbook.java:750)
问题分析
同一个Workbook创建CellStyle有最大数限制,其中.xls(Excel 97)
的最大数是4000,.xlsx(Excel 2007)
的最大数是64000 。
xls
参数限制于org.apache.poi.hssf.usermodel.HSSFWorkbook
:
private static final int MAX_STYLES = 4030;
public HSSFCellStyle createCellStyle() {
if (this.workbook.getNumExFormats() == MAX_STYLES) {
throw new IllegalStateException("The maximum number of cell styles was exceeded. You can define up to 4000 styles in a .xls workbook");
}
ExtendedFormatRecord xfr = this.workbook.createCellXF();
short index = (short)(getNumCellStyles() - 1);
return new HSSFCellStyle(index, xfr, this);
}
xlsx
参数限制于org.apache.poi.xssf.model.StylesTable
:
private static final int MAXIMUM_STYLE_ID = SpreadsheetVersion.EXCEL2007.getMaxCellStyles();// 64000
public XSSFCellStyle createCellStyle() {
if (getNumCellStyles() > MAXIMUM_STYLE_ID) {
throw new IllegalStateException("The maximum number of Cell Styles was exceeded. You can define up to " + MAXIMUM_STYLE_ID + " style in a .xlsx Workbook");
}
int xfSize = this.styleXfs.size();
CTXf xf = CTXf.Factory.newInstance();
xf.setNumFmtId(0L);
xf.setFontId(0L);
xf.setFillId(0L);
xf.setBorderId(0L);
xf.setXfId(0L);
int indexXf = putCellXf(xf);
return new XSSFCellStyle(indexXf - 1, xfSize - 1, this, this.theme);
}
因此,在生成Excel时,如果同一个Workbook不停的创建CellStyle,超限时就会产生样式最大数异常,最直接的体现就是在某些代码中,对每个单元格去独立的设置样式,生成大数据量的Excel报错。
解决方案
网上最热门的解决方案是所谓的将createCellStyle
放在循环外面,这只能应付表格样式单一的情况。
由于单元格样式CellStyle
并不是单元独立拥有的,每个单元格只是保存了样式的索引,一般的Excel真正使用到的样式也不会超过4000/64000
,因此更好的解决方案是实现单元格样式的复用(注意不同的Workbook创建的CellStyle是不能混用的)。
方案1:缓存样式实现复用
提取样式关键字作为key,将CellStyle缓存至Map:
Workbook workBook = new HSSFWorkbook();// or new XSSFWorkbook();
Sheet sheet = workBook.createSheet(strSheetName);
Map cellStyleMap = new HashMap<String, CellStyle>();// 缓存样式
// 样式代码
for (int rowIndex = 0; rowIndex < maxRow; rowIndex++) {
Row row = sheet.createRow(rowIndex);
for (int colIndex = 0; colIndex < maxCol; colIndex++) {
Cell cell = row.createCell((short) colIndex);
String styKey = getCellStyleKey(rowIndex, colIndex);// 根据获取样式key
CellStyle cellStyle = (CellStyle) cellStyleMap.computeIfAbsent(styKey, k-> workBook.createCellStyle());// 获取样式
cell.setCellStyle(cellStyle);
}
}
方案2:修改限制参数
修改POI中的限制参数( org.apache.poi.hssf.usermodel.HSSFWorkbook.MAX_STYLES
或org.apache.poi.ss.SpreadsheetVersion.EXCEL2007
)。
过多的创建样式会影响性能,建议仅在真正使用的样式超过限制时再去修改此参数。
方案3:延迟指定单元格样式实现复用
参见文章:
POI 操作Excel的单元格样式超过64000的异常问题解决
根据模版填充Excel并导出的工具 · GitCode