POI Excel单元格样式超过最大数(4000或64000)的解决方案
阅读原文时间:2023年07月09日阅读:4
aliases: []
tags   : " #QA #Java "
summary: [POI生成Excel超出的单元格样式的最大数量]
author : [yaenli]
notekey: [20230322-100908]

问题现象

使用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是不能混用的)。

提取样式关键字作为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);
    }
}

修改POI中的限制参数( org.apache.poi.hssf.usermodel.HSSFWorkbook.MAX_STYLESorg.apache.poi.ss.SpreadsheetVersion.EXCEL2007)。

过多的创建样式会影响性能,建议仅在真正使用的样式超过限制时再去修改此参数。

参见文章:

POI 操作Excel的单元格样式超过64000的异常问题解决

根据模版填充Excel并导出的工具 · GitCode