结论:XSSFReader+自定义DefaultHandler是Java处理GB级.xlsx的唯一稳定低内存方案;.xls须用HSSFEventFactory,但不支持公式重算和样式还原。因Workbook加载会全量解压XML致OOM,而SAX仅维持当前上下文,峰值内存约50MB。

直接说结论:用 XSSFReader + 自定义 DefaultHandler 是目前 Java 中唯一能稳定处理 GB 级 .xlsx 文件且内存压到 MB 级别的方案;.xls 文件则必须用 HSSFEventFactory,但要注意它不支持公式重计算和样式还原。
为什么不能用 Workbook 加载大 Excel
调用 new XSSFWorkbook(inputStream) 或 WorkbookFactory.create() 会把整个 .xlsx 解压后所有 XML(sharedStrings.xml、styles.xml、每个 sheet*.xml)全读进内存,GB 文件轻松吃掉 5–10 GB 堆空间,OutOfMemoryError: Java heap space 是必然结果。这不是配置调优能绕过的限制,是模型本身决定的。
-
.xlsx本质是 ZIP 包,里面每个 sheet 是一个扁平化 XML 流,没有行列索引结构 -
Workbook模式需构建完整对象图(Sheet → Row → Cell → Style/Formula/Value),内存占用与总单元格数正相关 - SAX 模式只在解析时维持当前 XML 元素上下文,峰值内存通常
XSSFReader 解析 .xlsx 的最小可行链路
核心是跳过 DOM 构建,直接绑定 ZIP 内部流 + SAX Handler。关键步骤缺一不可:
- 用
OPCPackage.open(inputStream)打开 ZIP,避免提前解压全部内容 - 用
XSSFReader获取SharedStringsTable和StylesTable—— 它们必须在解析 sheet 前加载,否则无法还原字符串或格式化数字 - 调用
XSSFReader.getSheetsData()得到每个 sheet 的InputStream,逐个喂给XMLReader - 自定义
DefaultHandler中,靠startElement()识别row、c(cell)、v(value)等标签,靠characters()拿原始文本值
示例关键片段:
XSSFReader reader = new XSSFReader(pkg); SharedStringsTable sst = new SharedStringsTable(); StylesTable styles = new StylesTable(); // 注意:必须先读取 sst 和 styles,否则 cell 值可能为空或格式错乱 XMLReader parser = XMLReaderFactory.createXMLReader(); parser.setContentHandler(new SheetHandler(sst, styles)); parser.parse(new InputSource(reader.getSheetsData().next()));
容易踩的坑:空单元格、类型丢失、数字变科学计数法
SAX 模式下,Excel 的“空单元格”不会出现在 XML 中(只有有值的 c 标签),row 标签里的 r 属性(如 r="A3")也不一定连续,直接按顺序取值会错位。
- 必须解析
c标签的r属性(如A3),用CellReference转成列索引,再填充到行数组对应位置,空位补null -
v标签内容是原始值:字符串存的是索引(需查sst),数字是 double 字面量(如123456789012345),日期是序列号(需用DateUtil转) - 如果没传
StylesTable,isNumeric或getCellType()逻辑就失效,所有数字都当字符串返回,1.23E8这种会原样透出 -
characters()可能被多次调用(XML 解析器分片推送文本),必须用StringBuilder缓存,endElement()时才提取
为什么 .xls(2003)不能套用同一套逻辑
.xls 是二进制 BIFF 格式,没有 XML 层,POI 提供的是 HSSFEventFactory + HSSFListener,原理类似 SAX 但事件粒度更粗(Record 级别而非元素级)。
- 必须监听
BOFRecord(开始表)、RowRecord、NumberRecord/LabelSSTRecord等,自己拼装行列结构 -
MissingCellDummyRecord和LastCellOfRowDummyRecord必须显式处理,否则空列直接跳过 - 不支持公式求值(
FormulaRecord只给 RPN 字节码,没内置计算器),outputFormulaValues = false时返回 #VALUE! - 依赖
POIFSFileSystem,对超大.xls(> 50MB)仍有 IO 压力,不如.xlsx的 ZIP 流式访问高效
真正的大报表场景,优先要求上游导出为 .xlsx;若只能收 .xls,建议加一层转换服务(用 LibreOffice headless 转成 .xlsx 再走 SAX 流程)。



















