必须用 sax 模式流式读取大 excel 文件,因 poi 默认 dom 模式将整表结构全载入内存致 oom;sxssfworkbook 仅支持写入,读取须用 xssfreader + 自定义 defaulthandler 解析关键标签,配合及时清理和规范写入。

直接用 XSSFWorkbook 或 HSSFWorkbook 读取几十万行以上的 Excel 文件,极易触发 OutOfMemoryError。根本原因不是数据本身太大,而是 POI 默认采用 DOM 模式——把整张表的结构、样式、公式、共享字符串表全部加载进内存,对象数量随行数线性甚至指数增长,且部分缓存(如 SharedStringsTable、CellStyle)长期驻留堆中,GC 难以回收。
必须改用流式读取(SAX 模式)
这不是“优化选项”,而是大数据量下的唯一可行路径。SXSSF 只适用于写入,**读取大文件不能靠 SXSSFWorkbook**——它不提供读能力。正确做法是绕过 Workbook 对象,用底层 SAX 解析器逐标签处理:
- 使用
XSSFReader获取底层 XML 流,配合XMLReader和自定义DefaultHandler - 只监听关键标签:
<row></row>、<c></c>(单元格)、<v></v>(值),解析完一行就交给业务逻辑,不保留 Row/Cell 实例 - 共享字符串表需提前加载一次(
new XSSFReader(inputStream).getSharedStringsTable()),但它只占固定内存,不随行数增加 - 在
endElement中清空StringBuilder等临时缓冲,避免字符内容持续累积
写入端也要规范 SXSSF 清理
如果场景是“读—加工—写”,写入环节仍可能泄漏:
- 创建
SXSSFWorkbook必须显式指定窗口大小:new SXSSFWorkbook(1000),避免默认 100 行导致频繁刷盘和反序列化开销 - 每写入 500–1000 行后调用
sheet.flushRows(),主动将旧行刷出内存 - 写完必须调用
workbook.dispose(),不只是close()——否则临时 ZIP 文件句柄和磁盘缓存不会释放 - 输入流务必用
try-with-resources包裹,或手动close()
避开 POI 的隐性陷阱
即使用了流式读取,混用传统 API 也会让努力白费:
- 禁止在 SAX 处理器里调用
workbook.getSheetAt(0).getRow(i),这会强制触发全量加载 - 不要缓存
Row、Cell或Workbook实例——它们不属于流式 API,一创建就引入 DOM 开销 - 用
WorkbookFactory.create(inputStream, true)替代WorkbookFactory.create(inputStream)(POI 5.2+ 支持,true表示启用流式模式) - 避免全局静态持有
CellStyle;复用样式需通过workbook.createCellStyle()创建后缓存,但注意它关联 workbook 生命周期
核心就一条:把“全量加载”换成“边读边扔”。内存占用不再取决于总行数,而只取决于单行解析所需空间,真正实现 O(1) 级别稳定。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











