如何在使用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
相关产品推荐
相关产品推荐

