You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Apache POI生成Excel的性能优化技术问询

Apache POI生成Excel性能优化方案(10k行耗时5分钟问题修复)

我们基于指定模板生成Excel工作簿,源文件为制表符分隔文本,当前使用Java+Apache POI实现功能,但处理约10k行的源文件时耗时达5分钟,性能未达预期。以下是针对性的优化方案:


现有代码的核心性能瓶颈

  1. 重复字符串分割:循环中多次调用MT940Text.split("\\r\\n"),每次都要重新分割整个文本,10k行数据会产生10k次重复分割操作,开销极大。
  2. 全量加载文件:一次性将整个文件读入字符串,不仅占用内存,还无法边读边处理,延迟了数据处理的启动时间。
  3. 未启用POI流式处理:使用默认的XSSFWorkbook处理大数据量时,所有数据都驻留内存,导致内存占用高、写入速度慢。
  4. 单元格操作冗余:循环中逐行逐单元格创建和赋值,未做批量优化,且字符串分割使用正则表达式(split("\\t"))累积开销。

具体优化措施

1. 一次性预处理所有行,避免重复分割

将文本分割操作提前执行一次,存储为数组后循环复用,彻底消除重复分割的开销:

// 原代码(低效)
int _tableSize = MT940Text.split("\\r\\n").length;
for (int j = 0; j < _tableSize; j++) {
    String _transactionInCtx = MT940Text.split("\\r\\n")[j];
    // ...
}

// 优化后(高效)
String[] allLines = MT940Text.split("\\r\\n");
int _tableSize = allLines.length;
for (int j = 0; j < _tableSize; j++) {
    String _transactionInCtx = allLines[j];
    // ...
}

2. 流式读取文件,减少内存占用

替换全量读取为逐行流式读取,降低内存消耗的同时,可提前启动数据处理:

private static List<String> readFileLines(final String fileName) {
    List<String> lines = new ArrayList<>();
    try (BufferedReader br = new BufferedReader(new FileReader(fileName, StandardCharsets.UTF_8))) {
        String line;
        while ((line = br.readLine()) != null) {
            lines.add(line);
        }
    } catch (IOException e) {
        log.error(printStackTraceToString("readFileLines", "", e));
        return Collections.emptyList();
    }
    return lines;
}

// 在generateMT940Excel中调用
List<String> allLines = readFileLines(inFileName);
int _tableSize = allLines.size();
for (int j = 0; j < _tableSize; j++) {
    String _transactionInCtx = allLines.get(j);
    // ...
}

3. 切换为SXSSF流式处理模式

SXSSFWorkbook是Apache POI专为大数据量设计的实现,它会将超出内存缓存的行写入临时磁盘文件,大幅降低内存占用并提升写入速度:

// 读取XSSF模板
XSSFWorkbook xssfTemplate = new XSSFWorkbook(
        ZDMT940FlatToExcel.class.getClassLoader().getResourceAsStream("EBS_Result_Template.xlsx"));
// 克隆模板工作表并修改内容
XSSFSheet templateSheet = xssfTemplate.getSheet("##BankName##");
XSSFSheet clonedXssfSheet = xssfTemplate.cloneSheet(xssfTemplate.getSheetIndex(templateSheet), bankName);
clonedXssfSheet.getRow(0).getCell(0).setCellValue(
        templateSheet.getRow(0).getCell(0).toString().replace("##BankName##", bankName).replace("##EBSType##", "MT940"));
clonedXssfSheet.getRow(2).getCell(3).setCellValue(
        templateSheet.getRow(2).getCell(3).toString().replace("##TimeStamp##", _timeNow));
// 转换为SXSSFWorkbook,设置内存缓存行数(此处设为100,可根据内存调整)
SXSSFWorkbook wb = new SXSSFWorkbook(xssfTemplate, 100);
wb.setCompressTempFiles(true); // 压缩临时文件,减少磁盘IO
// 获取流式工作表
SXSSFSheet clonedSheet = wb.getSheet(bankName);

4. 替换正则分割为高效的字符串拆分

用StringTokenizer替代split("\\t"),避免正则表达式的性能开销:

// 原代码
String[] _transactionInCtxSize = _transactionInCtx.split("\\t");

// 优化后
StringTokenizer tokenizer = new StringTokenizer(_transactionInCtx, "\t");
List<String> cellValues = new ArrayList<>(maxColumns); // 提前指定容量
while (tokenizer.hasMoreTokens()) {
    cellValues.add(tokenizer.nextToken());
}
// 补全空列(保证与最大列数一致)
while (cellValues.size() < maxColumns) {
    cellValues.add(" ");
}

5. 预计算最大列数,批量创建单元格

提前遍历所有行计算最大列数,确保每行创建足够的单元格,避免样式缺失的同时减少单元格创建的冗余操作:

// 预计算最大列数
int maxColumns = 0;
for (String line : allLines) {
    StringTokenizer tokenizer = new StringTokenizer(line, "\t");
    maxColumns = Math.max(maxColumns, tokenizer.countTokens());
}

// 批量创建单元格
for (int j = 0; j < allLines.size(); j++) {
    int _rowNum = baseRowForTransactions + j;
    Row _rowInCxt = clonedSheet.createRow(_rowNum);
    StringTokenizer tokenizer = new StringTokenizer(allLines.get(j), "\t");
    int colIndex = 0;
    while (tokenizer.hasMoreTokens()) {
        Cell _cellInCxt = _rowInCxt.createCell(colIndex);
        _cellInCxt.setCellStyle(borderStyle);
        _cellInCxt.setCellValue(nvl(tokenizer.nextToken(), " "));
        colIndex++;
    }
    // 补充剩余空单元格
    while (colIndex < maxColumns) {
        Cell _cellInCxt = _rowInCxt.createCell(colIndex);
        _cellInCxt.setCellStyle(borderStyle);
        _cellInCxt.setCellValue(" ");
        colIndex++;
    }
}

6. 优化输出流与POI版本

  • 使用缓冲输出流减少磁盘IO次数:
    fileOut = new BufferedOutputStream(new FileOutputStream(outFileName));
    
  • 升级至最新稳定版Apache POI(如5.2.5+),新版本修复了大量性能问题。

内容的提问来源于stack exchange,提问作者Srii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 01:32:02