EasyExcel处理大数据量导出
1、简述
在使用 EasyExcel 处理大数据量导出时,主要需要考虑内存占用和性能问题。EasyExcel 相比传统 POI 库的优势在于其采用流式处理,避免将全部数据加载到内存中,非常适合大数据量导出场景。
2、处理方法
处理Excel大数据量导出时,传统方式容易导致内存溢出(OOM)。EasyExcel通过一些设计很好地解决了这个问题。下面是一个核心策略和注意事项的表格总结,帮助你快速了解:
特性维度 | 传统POI (HSSF/XSSF) | EasyExcel (优化方案) |
---|---|---|
核心机制 | 对象模型 (DOM) - 全量加载到内存 | 流式写入 & 事件驱动 (SAX) |
内存管理 | 每个Cell约1KB,百万数据GB级内存占用,易OOM | 分批次处理(默认100行),对象复用,磁盘缓存(临时文件),内存占用低(~50MB下) |
性能表现 | 数据量超5万行后性能可能急剧下降 | 平稳处理百万级数据 |
API与易用性 | 直接操作对象树,相对直观但大数据时繁琐 | 基于注解或简单API,封装复杂逻辑,使用简便 |
样式支持 | 支持复杂样式 | 支持基本样式,复杂样式需自定义WriteHandler |
- 流式写入(Streaming Write)
- 通过 Apache POI 的 SXSSFWorkbook 实现,将数据分批次写入磁盘,而非一次性加载到内存。
- 默认每 100 行数据生成一个临时文件块,避免内存溢出(OOM)。
- 事件驱动模型:
- 通过注解驱动(如 @ExcelProperty)自动解析数据模型,生成表头和内容。
- 使用模板引擎动态填充数据,支持复杂格式和样式。
- 内存优化:
- 对象复用:复用 Cell 和 Row 对象,减少 JVM 垃圾回收压力。
- 分片处理:按需加载数据,适合大数据量场景(如百万级数据导出)。
EasyExcel避免OOM的核心原理
EasyExcel 之所以能有效避免内存溢出,核心在于其流式处理机制和内存优化设计,主要体现在以下几点:
- 按需加载数据:不一次性将所有数据加载到内存,而是分批次读取和写入,每次仅处理部分数据。
- 基于 SXSSF 的滑动窗口:底层采用 POI 的 SXSSFWorkbook,通过设置滑动窗口大小(如setRandomAccessWindowSize(n)),只在内存中保留最近的 n 行数据,超过的自动写入临时磁盘文件,大幅减少内存占用。
- 即时释放资源:写入一批数据后可立即清理列表,配合自动刷新机制,避免数据在内存中堆积。
- 无模型映射优化:支持不定义实体类的写法,减少对象创建带来的内存开销。
这些设计让 EasyExcel 能在处理几十万甚至上百万行数据时,始终将内存占用控制在较低水平,从而有效避免传统 POI 因加载全部数据导致的内存溢出问题。
SXSSFWorkbook参数配置
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), LargeData.class).registerWriteHandler(new WorkbookWriteHandler() {@Overridepublic void afterWorkbookCreate(WriteWorkbookHolder writeWorkbookHolder) {Workbook workbook = writeWorkbookHolder.getWorkbook();if (workbook instanceof SXSSFWorkbook) {((SXSSFWorkbook) workbook).setCompressTempFiles(true); // 启用临时文件压缩}WorkbookWriteHandler.super.afterWorkbookCreate(writeWorkbookHolder);}}).build();
EasyExcel 底层在处理大数据时,会使用SXSSFWorkbook
(POI 的流式处理类),它会将超出指定行数的数据写入临时文件,而不是全部保存在内存中。通过设置setRandomAccessWindowSize(n)
,可以控制内存中最多保留 n 行数据,超过的行会被写入磁盘临时文件,从而有效控制内存占用。不过EasyExcel 本身不直接提供修改滑动窗口大小的 API。
3、示例代码
package com.ybw.write;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.ybw.entity.LargeData;
import com.ybw.service.BigDataService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;import java.util.List;/*** @author ybw* @version V1.0* @className WriteExcel* @date 2025/8/25**/
@Component
@Slf4j
public class WriteExcel {@Resourceprivate BigDataService bigDataService;public void writeLargeData() {String fileName = "large_data.xlsx";// 初始化ExcelWritertry (ExcelWriter excelWriter = EasyExcel.write(fileName, LargeData.class).build()) {//1、创建WriteSheetWriteSheet writeSheet = EasyExcel.writerSheet("测试数据").build();//2、初始化数据,从第一页开始,每页获取5000条int pageSize = 5000;int pageIndex = 1;//3、分页处理数据doHandleExcel(pageIndex, pageSize, excelWriter, writeSheet);//4、非常重要:关闭资源,此时才会真正写入磁盘并合并临时文件excelWriter.finish();}}/*** 分页处理数据(递归)** @param pageIndex 页码* @param pageSize 每页大小* @param excelWriter ExcelWriter* @param writeSheet WriteSheet* @methodName: doHandleExcel* @return: void* @author: ybw* @date: 2025/8/25**/private void doHandleExcel(int pageIndex, int pageSize, ExcelWriter excelWriter, WriteSheet writeSheet) {//1、分页查询数据库:使用你的MyBatis或JPA方法,这里是示例List<LargeData> dataList = bigDataService.getDataByPage(pageIndex, pageSize);if (CollectionUtils.isEmpty(dataList)) {// 没有数据,结束循环return;}//2、写入当前批次数据excelWriter.write(dataList, writeSheet);//3、手动清理列表,释放内存dataList.clear();//4、继续处理下一页次数据doHandleExcel(++pageIndex, pageSize, excelWriter, writeSheet);}
}
- 分页查询数据库:避免大数据造成java大对象,造成内存溢出。
- excelWriter.write(dataList, writeSheet):数据并不会立即直接写入最终的Excel文件磁盘位置,而是会先写入到临时文件中。这个过程是 EasyExcel 能够高效处理大数据量且避免内存溢出(OOM)的关键设计。
- excelWriter.finish():关闭资源,此时才会真正写入磁盘并合并临时文件。
特性维度 | excelWriter.write(dataList, writeSheet) | excelWriter.finish() |
---|---|---|
核心作用 | 将数据分批写入临时文件 | 合并所有临时文件,生成最终Excel文件,并清理临时文件 |
数据去向 | 内存(对象复用池) → 磁盘临时文件 (默认每100行一个临时文件块) | 临时文件 → 最终的目标Excel文件 |
内存管理 | 采用流式写入与对象复用机制,显著降低内存占用 | 释放整个写入过程中占用的内存资源 |
是否必须 | 是(多次调用,用于循环写入分页查询的数据) | 是(且必须调用,否则会导致临时文件残留且最终文件不完整或不可用) |
临时文件状态 | 写入过程中产生 | 完成后自动清理 |
4、结合文件服务器
核心思路是
将耗时的、消耗资源的大数据量导出过程与主应用服务分离,通过异步方式在后台生成文件,并上传到文件服务器,从而释放主应用服务器的资源(特别是内存和CPU)。
对比传统方案
方案 | 核心流程 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
同步导出(传统方式) | 用户请求 → 应用服务器实时处理并导出 → 浏览器下载 | 实现简单,即时反馈 | 极易导致应用服务器OOM,请求超时,阻塞其他用户请求 | 数据量极小(<1万行)的导出 |
异步导出+文件服务器(推荐) | 用户触发 → 记录任务状态为“处理中” → 后台异步线程导出 → 上传至文件服务器(如OSS/S3) → 更新状态为“完成”并提供下载链接 | 避免OOM,不阻塞请求,用户体验好,支持超大数据量,下载链接减轻服务器带宽压力 | 架构稍复杂,需要实现任务状态跟踪和文件管理 | 所有大数据量(>1万行)的导出场景 |
优势
- 根本解决OOM:应用服务器只负责生成一个临时文件,然后立即上传并删除,内存压力极小。
- 高可用和可扩展:即使应用服务器重启,异步任务也可以通过记录恢复。文件服务器本身具备高可用性。
- 提升用户体验:用户无需长时间等待浏览器卡住,可以随时查看任务进度。
- 卸载带宽压力:用户直接从文件服务器下载大文件,不消耗应用服务器的出口带宽。
参考文章:
为什么EasyExcel能处理大数据量而不内存溢出,EasyExcel原理-EW帮帮网
Java EasyExcel导出报表内存溢出全解析 🚀-腾讯云开发者社区-腾讯云