Apache POI SXSSFWorkbook生成Excel损坏 低内存修复方案
SXSSF生成大体积Excel被第三方系统判定损坏的低内存解决方案
问题复现与已知现象
- 技术栈:Apache POI 5.2.0,服务器内存资源受限
- 实现方案:2万条及以上量级Excel导出采用
SXSSFWorkbook做低内存流式写入,生成的文件可直接被Microsoft Excel正常打开、编辑 - 异常表现:
- 原始生成文件上传第三方业务系统时被校验拦截,提示「文件已损坏」
- 原始文件经Excel手动打开、无修改直接保存后,再次上传可正常通过校验
- 已验证不可落地方案:
用XSSFWorkbook全量读取原始文件后直接关闭,输出的文件可通过校验,但大文件场景下会将全量记录加载至内存,直接触发OOM导致任务崩溃,无法生产落地 - 观测结论:SXSSF直接生成的文件体积小于Excel手动保存的同内容文件,二者OOXML结构存在差异,需明确差异定位方法与低内存修复路径
文件差异定位方法
.xlsx本质为遵循OOXML规范的ZIP压缩包,无需全量加载文件即可完成结构对比:
- 将SXSSF生成的原始文件、Excel手动保存的正常文件分别修改后缀为
.zip,解压至两个独立目录 - 按以下优先级对比差异:
- 首先比对压缩包内的文件列表,确认是否存在缺失的配置条目,重点检查
[Content_Types].xml的注册项、docProps目录下的核心属性文件是否完整 - 对同名XML文件做文本diff,重点核查三类高频差异点:
- 工作表文件中
<dimension>标签的ref属性:SXSSF流式写入时默认不会统计全表单元格范围,常固定写为A1,与实际数据范围不符;Excel打开保存时会自动重算该值,部分第三方校验逻辑会强制校验该字段合法性 - XML标签与命名空间完整性:SXSSF流式写入时若流关闭顺序错误,可能出现标签未闭合、命名空间声明遗漏的问题,Excel容错性高可自动修复,但严格校验的第三方系统会直接判定损坏
- 共享字符串表
sharedStrings.xml的计数属性:SXSSF默认写入的字符串总数字段可能与实际条目数存在偏差,同样会触发部分校验逻辑拦截
- 工作表文件中
- 最后比对压缩条目元数据:SXSSF默认使用的压缩级别、条目存储属性与Excel保存时的默认参数存在差异,部分老旧校验逻辑会校验该类元数据
- 首先比对压缩包内的文件列表,确认是否存在缺失的配置条目,重点检查
低内存落地修复方案
全程避免全量加载文件内容,内存占用稳定在MB级,支持十万级以上大文件处理:
- 写入阶段前置规避(优先采用)
从SXSSF写入逻辑层面修正已知结构问题,无需后置二次处理:- 写入完成前主动补全工作表维度:遍历写入过程中记录的最大行号、最大列号,生成对应的单元格范围引用,调用
sheet.setDimensionReference(CellRangeAddress.valueOf(startCellRef + ":" + endCellRef))手动设置正确的维度值 - 初始化
SXSSFWorkbook时传入合理的内存窗口参数,开启临时文件压缩:new SXSSFWorkbook(200, true),该配置下内存中仅保留200行数据,其余数据写入磁盘临时文件,写入结束后POI会自动补全所有XML命名空间与闭合标签 - 写入完成前主动补全文档核心属性:调用
workbook.getXSSFWorkbook().getProperties().getCoreProperties().setCreator("Apache POI")设置文档创建者等核心字段,避免因属性缺失触发校验拦截 - 严格遵循流关闭顺序:先调用
workbook.write(outputStream),再依次关闭outputStream、调用workbook.close()、调用workbook.dispose()清理临时文件,避免流截断导致的文件结构损坏
- 写入完成前主动补全工作表维度:遍历写入过程中记录的最大行号、最大列号,生成对应的单元格范围引用,调用
- 后置流式修复(兼容存量生成文件)
对已经生成的SXSSF文件,采用JDK原生ZIP流做逐条目重写,全程单条处理压缩包内容,内存占用不随文件大小增长:// 核心修复逻辑,无第三方依赖,常规文件处理内存占用<20MB byte[] buffer = new byte[8192]; try (ZipInputStream zis = new ZipInputStream(new FileInputStream(originXlsxPath)); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(fixedXlsxPath))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { // 过滤SXSSF生成的无效临时条目 if (entry.getName().contains("tmp/") || entry.isDirectory()) { zis.closeEntry(); continue; } // 新建压缩条目,统一使用与Excel一致的DEFLATED压缩算法 ZipEntry newEntry = new ZipEntry(entry.getName()); newEntry.setMethod(ZipEntry.DEFLATED); zos.putNextEntry(newEntry); int len; while ((len = zis.read(buffer)) > 0) { zos.write(buffer, 0, len); } zos.closeEntry(); zis.closeEntry(); } // 若对比发现缺失必要配置项,可在此处流式补充写入对应XML内容 } - 高兼容兜底方案
若第三方系统校验逻辑极其严格,可采用SAX模式的流式Excel读写组件做逐行读取、逐行写入,全程内存中仅保留单行数据,相比XSSFWorkbook全量加载内存占用降低99%,20万条量级文件处理内存可稳定控制在100MB以内。
内容的提问来源于stack exchange,提问作者Enzo Terranova
相关产品推荐
相关产品推荐

