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

如何在使用Apache POI写入大型Excel文件时更新进度条?

嘿,这个问题我之前做Excel批量导出的时候碰到过,监控文件大小确实挺麻烦的,还容易因为文件系统缓存导致进度不准,给你几个更简洁高效的实现思路:

最优方案:在数据写入阶段拆分批次更新进度

其实最直接靠谱的方式是把addSubstentialAmountOfDataToWorkbook里的大数据写入逻辑拆分成小批次,每完成一批就更新一次进度条。毕竟你自己清楚要写入的数据总量,拆分后进度计算完全准确,代码也不冗余。

举个实际代码例子:

var workbook = new SXSSFWorkbook();
Sheet dataSheet = workbook.createSheet("LargeData");

// 假设要写入10万行数据,拆成10个批次
int totalRows = 100000;
int batchSize = 10000;
int progressPerBatch = 100 / (totalRows / batchSize); // 每个批次对应10%进度

for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
    Row row = dataSheet.createRow(rowIndex);
    // 填充当前行的单元格数据(比如row.createCell(0).setCellValue("xxx"))
    
    // 每写完一个批次就更新进度
    if ((rowIndex + 1) % batchSize == 0) {
        progressBar.add(progressPerBatch);
    }
}

// 最后确保进度条拉满到100%(避免整除余数导致的进度不足)
progressBar.add(100 - progressBar.getProgress());

// 后续的write和close逻辑不变
workbook.write(outputStream);
workbook.close();

这个方案的优势是进度完全可控,没有误差,代码改动也很小,不需要额外的监控逻辑

备选方案:自定义OutputStream监控写入进度

如果你的数据写入逻辑没办法拆分(比如调用的是第三方封装好的方法),那可以包装一个OutputStream,在写入字节的过程中统计已写入量,对比预估的总大小来更新进度。

示例代码:

var workbook = new SXSSFWorkbook();
addSubstentialAmountOfDataToWorkbook(workbook);

// 先预估总输出大小(可以根据数据量估算,比如每行平均占100字节,10万行就是10^7字节)
long estimatedTotalSize = 10 * 1024 * 1024; // 示例:预估10MB
long writtenBytes = 0;
int lastReportedProgress = 0;

// 包装原始输出流,统计写入字节数
var progressTrackingStream = new FilterOutputStream(outputStream) {
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        super.write(b, off, len);
        writtenBytes += len;
        
        // 计算当前进度,避免频繁更新(比如只在进度变化超过1%时更新)
        int currentProgress = (int) ((writtenBytes * 100) / estimatedTotalSize);
        if (currentProgress > lastReportedProgress) {
            progressBar.add(currentProgress - lastReportedProgress);
            lastReportedProgress = currentProgress;
        }
    }
};

workbook.write(progressTrackingStream);
progressTrackingStream.close();
workbook.close();

这个方案的好处是不需要改动数据写入逻辑,缺点是需要预估总大小,可能存在小误差,但如果你的数据格式比较固定,误差基本可以忽略。

进阶方案:重写SXSSF的flush逻辑

SXSSFWorkbook是通过内存窗口+临时文件的方式处理大数据的,当内存中的行数达到阈值(默认100行)时,会自动flush到临时文件。我们可以重写SXSSFSheet的flushRows方法,每次flush时更新进度。

示例代码:

// 自定义SXSSFSheet,监听flush事件
class ProgressSXSSFSheet extends SXSSFSheet {
    private final ProgressBar progressBar;
    private final int totalRows;
    private int flushedRowCount = 0;

    public ProgressSXSSFSheet(XSSFSheet sheet, int windowSize, ProgressBar progressBar, int totalRows) {
        super(sheet, windowSize);
        this.progressBar = progressBar;
        this.totalRows = totalRows;
    }

    @Override
    protected void flushRows(int remaining) throws IOException {
        super.flushRows(remaining);
        // 每次flush的行数就是内存窗口大小
        flushedRowCount += getRandomAccessWindowSize();
        int currentProgress = (int) ((flushedRowCount * 100) / totalRows);
        progressBar.add(currentProgress - progressBar.getProgress());
    }
}

// 使用时的代码
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
XSSFSheet rawSheet = xssfWorkbook.createSheet("Data");
// 创建带进度跟踪的Sheet,指定内存窗口大小、进度条、总行数
ProgressSXSSFSheet trackedSheet = new ProgressSXSSFSheet(rawSheet, 100, progressBar, 100000);
var workbook = new SXSSFWorkbook(xssfWorkbook, 100);

// 往trackedSheet里写入数据,每次flush都会自动更新进度
addSubstentialAmountOfDataToWorkbook(workbook);

workbook.write(outputStream);
workbook.close();

这个方案比较适合深度定制的场景,但代码相对复杂一些,一般情况下前两个方案足够用了。


内容的提问来源于stack exchange,提问作者Erez Ben Harush

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 03:04:07