解决方案:将 XSSFWorkbook 转换为 SXSSFWorkbook 避免 OOM
1. 为什么 SXSSFWorkbook 能解决 OOM?
XSSFWorkbook:将整个 Excel 文件加载到内存,数据量较大时(如超过 10 万行)会导致 内存溢出(OOM)。SXSSFWorkbook(Streaming Version ofXSSFWorkbook):- 仅保留最近 N 行 (由
rowAccessWindowSize参数控制)在内存中,其余数据写入临时文件。 - 通过 磁盘空间换内存空间,避免 OOM。
- 适用于大数据量导出(百万级数据)。
- 仅保留最近 N 行 (由
2. 如何将 XSSFWorkbook 转换为 SXSSFWorkbook?
场景 1:导出 Excel(新建文件)
java
`// 直接使用 SXSSFWorkbook 替代 XSSFWorkbook
try (SXSSFWorkbook wb = new SXSSFWorkbook(100); // 保留最近 100 行在内存
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// 创建 Sheet
SXSSFSheet sheet = wb.createSheet("Sheet1");
// 写入表头(同 XSSFWorkbook)
XSSFRow headerRow = (XSSFRow) sheet.createRow(0);
headerRow.createCell(0).setCellValue("Name");
headerRow.createCell(1).setCellValue("Age");
// 写入数据(循环写入)
for (int i = 0; i < 100000; i++) {
XSSFRow dataRow = (XSSFRow) sheet.createRow(i + 1);
dataRow.createCell(0).setCellValue("User" + i);
dataRow.createCell(1).setCellValue(20 + i);
// 可选:手动刷新行到磁盘(减少内存占用)
if (i % 1000 == 0) {
((SXSSFSheet) sheet).flushRows(100); // 保留最近 100 行
}
}
wb.write(outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}`
场景 2:读取大 Excel 文件(避免 OOM)
如果需要读取大 Excel 文件并处理,建议直接使用 XSSFReader(SAX 模式) 或 EasyExcel ,因为 SXSSFWorkbook 主要用于 写入 ,而非读取。
但若必须用 SXSSFWorkbook 处理大文件,可以分段读取:
java
`try (XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new FileInputStream("large_file.xlsx"));
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook, 100)) {
SXSSFSheet sheet = (SXSSFSheet) sxssfWorkbook.getSheetAt(0);
for (Row row : sheet) {
// 处理每一行(注意:SXSSFWorkbook 读取时可能已丢失部分行,因为它们被写入磁盘)
System.out.println(row.getCell(0).getStringCellValue());
}
} catch (IOException e) {
e.printStackTrace();
}`
⚠️ 注意 :
SXSSFWorkbook读取时可能无法访问所有行(因为部分行已被写入临时文件),因此 读取大文件建议用XSSFReader或EasyExcel。
3. 关键优化点
-
设置
rowAccessWindowSize:java`SXSSFWorkbook wb = new SXSSFWorkbook(100); // 内存中保留 100 行`- 值越小,内存占用越低,但磁盘 I/O 越频繁。
- 默认值是
100,可根据数据量调整(如1000)。
-
手动调用
flushRows():java`((SXSSFSheet) sheet).flushRows(100); // 强制将行写入磁盘`- 适用于循环中定期释放内存。
-
关闭资源 :
java`try (SXSSFWorkbook wb = new SXSSFWorkbook(100)) { // ...操作 } // 自动调用 dispose() 删除临时文件`-
必须调用
dispose(),否则临时文件可能残留:java`wb.dispose(); // 删除临时文件`
-
-
禁用临时文件(仅适用于小数据量) :
java`SXSSFWorkbook wb = new SXSSFWorkbook(null, 100, true, true); // 最后一个参数 true 表示不生成临时文件(全部保留在内存)`
4. 对比 XSSFWorkbook 和 SXSSFWorkbook
| 特性 | XSSFWorkbook |
SXSSFWorkbook |
|---|---|---|
| 内存占用 | 高(全部数据在内存) | 低(仅保留部分行) |
| 适用场景 | 小数据量(< 10 万行) | 大数据量(> 10 万行) |
| 临时文件 | 无 | 生成临时文件(需调用 dispose()) |
| 读取大文件 | 支持 | 不推荐(可能丢失数据) |
| 性能 | 快 | 稍慢(因磁盘 I/O) |
5. 扩展:使用 EasyExcel(阿里开源)
如果项目允许引入第三方库,EasyExcel 是更好的选择:
-
内存优化:基于 SAX 模式解析 Excel,几乎无内存限制。
-
简单 API :
java`// 写入 EasyExcel.write("output.xlsx", DemoData.class).sheet("Sheet1").doWrite(dataList); // 读取 EasyExcel.read("input.xlsx", DemoData.class, new DemoDataListener()).sheet().doRead();`
总结
- 导出大数据量 Excel :用
SXSSFWorkbook替代XSSFWorkbook,并设置rowAccessWindowSize。 - 读取大 Excel 文件 :用
XSSFReader(SAX 模式)或EasyExcel。 - 及时释放资源 :调用
dispose()删除临时文件。 - 终极方案 :考虑
EasyExcel进一步简化代码并提升性能。
通过以上优化,可彻底解决 XSSFWorkbook 导致的 OOM 问题!