大文件上传导致OOM

1.背景:

上传文件较大excel,使用WorkbookFactory.create(inputStream)全在内存处理导致OOM。文件数据不多,但包含了很多冗余信息。

2.原因分析:

  1. WorkbookFactory.create(inputStream)是 Apache POI 中用于解析 Excel 文件的核心方法。然而,这个方法会将整个 Excel 文件加载到内存中。所以文件比较大的时候,会导致OOM。

  2. HSSFWorkbook 和 XSSFWorkbook 的内存占用

  • 如果是 .xls 文件(HSSFWorkbook),Apache POI 会在内存中构建一个完整的对象模型。

  • 如果是 .xlsx 文件(XSSFWorkbook),尽管它是基于 XML 的格式,但仍然可能占用大量内存,尤其是在文件中存在大量空白单元格、样式、公式等。

  1. 一次性加载文件

  • WorkbookFactory.create(inputStream) 默认会一次性将整个文件加载到内存中,而不是流式读取。

  1. 文件冗余内容

  • 即使数据量不多,文件中可能存在大量无用的空白行、隐藏列、样式等,导致内存占用过高。


3.解决方案:

1. 使用 SXSSFWorkbook(适用于 .xlsx 文件)
  • SXSSFWorkbook 是 Apache POI 提供的流式处理类,适合处理大文件。

  • 它不会将整个文件加载到内存中,而是只保留一部分数据在内存中,其余数据写入磁盘。

  • 示例代码:

import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

// 将输入流转换为 SXSSFWorkbook
Workbook workbook;
if (fileName.endsWith(".xlsx")) {
    workbook = new SXSSFWorkbook(new XSSFWorkbook(inputStream), 100); // 保留 100 行在内存中
} else {
    workbook = WorkbookFactory.create(inputStream);
}

2. 使用 EasyExcel(推荐)
  • 阿里开源的 EasyExcel 是一个轻量级的 Excel 解析库,支持流式读取,内存占用极低。

  • 示例代码:

import com.alibaba.excel.EasyExcel;

String fileName = "example.xlsx";
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
  • DemoData 是数据模型类,DemoDataListener 是自定义的数据处理监听器。

3. 转换为 CSV 格式
  • 如果可以接受用户上传 CSV 文件,建议优先使用 CSV 格式,因为它的解析效率更高,且不包含样式、公式等额外信息。

  • 示例代码:

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
    String[] data = line.split(",");
    // 处理数据
}

4. 增加 JVM 堆内存
  • 如果必须使用 WorkbookFactory.create(inputStream),可以增加 JVM 堆内存以避免 OOM。

  • 修改 JVM 参数:

  • 注意:这只是临时解决方案,可能会掩盖潜在的问题。

5. 检查并优化文件
  • 清理空白行/列:确保文件中没有多余的空白行和列。

  • 移除隐藏工作表:删除不需要的工作表。

  • 清除格式化信息:减少样式、公式、注释等内容。

  • 另存为新文件:保存为新的 .xlsx 文件,通常可以减少文件大小。

6. 分片处理
  • 如果文件过大,可以在前端或后端实现分片逻辑,分批次读取和处理数据。

  • 后端示例:

InputStream inputStream = new FileInputStream("large_file.xlsx");
Workbook workbook = WorkbookFactory.create(inputStream);

Sheet sheet = workbook.getSheetAt(0);
int batchSize = 1000; // 每次处理 1000 行
for (int i = 0; i <= sheet.getLastRowNum(); i += batchSize) {
    List<Row> batchRows = new ArrayList<>();
    for (int j = i; j < i + batchSize && j <= sheet.getLastRowNum(); j++) {
        batchRows.add(sheet.getRow(j));
    }
    processBatch(batchRows); // 处理当前批次数据
}

总结

  • 优先推荐使用 EasyExcel,它专为大文件设计,内存占用极低。

  • 如果必须使用 Apache POI,改用 SXSSFWorkbook 或对文件进行预处理。

  • 调整 JVM 参数是临时解决方案,无法从根本上解决问题。

  • 在前端限制文件大小和格式,避免不必要的性能开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值