java - Apache POI SXSSFWorkbook 在 XLSX 文件内存问题中写入大约 500.000

标签 java excel apache-poi spring-batch

我们使用 Spring Batch 和 Apache POI 开发了一个应用程序,用于从 DB(Oracle) 读取数据并将其写入 apache-poi 生成的 XLSX 文件中。
示例下方有一个读取器(从 DB ItemReader 读取数据)和写入器(ItemWriter)的作业:

@BeforeStep
public void beforeStep(StepExecution stepExecution) {
    log.info("Calling analytic beforeStep");

    cellProperties = new HashMap<String, Object>();
    cellProperties.put(CellUtil.BORDER_LEFT, BorderStyle.THIN);
    cellProperties.put(CellUtil.BORDER_RIGHT, BorderStyle.THIN);
    cellProperties.put(CellUtil.BORDER_BOTTOM, BorderStyle.THIN);
    cellProperties.put(CellUtil.BORDER_TOP, BorderStyle.THIN);

    outputFilename = stepExecution.getJobExecution().getExecutionContext().getString("fileName");
    File xlsxFile = new File(outputFilename);

    if (xlsxFile.exists() && !xlsxFile.isDirectory()) {
        log.info("ViewReportSalesAnalyticsExcelWriter File exist");
        InputStream fileIn = null;
        try {
            fileIn = new BufferedInputStream(new FileInputStream(xlsxFile), 100);
            workbook = new SXSSFWorkbook(new XSSFWorkbook(fileIn), 100);
        } catch (Exception e) {
            log.error("ViewReportSalesAnalyticsExcelWriter error: ", e);
        } finally {
            if (fileIn != null) {
                try {
                    fileIn.close();
                } catch (IOException e) {
                    log.error("ViewReportSalesAnalyticsExcelWriter error: ", e);
                }
            }
        }
    } else {
        log.info("ViewReportSalesAnalyticsExcelWriter File not exist, creating");
        workbook = new SXSSFWorkbook(100);
    }

    // temp files will be gzipped
    workbook.setCompressTempFiles(true);
    Sheet sheet = workbook.createSheet("Report POG - Analitico");
    sheet.setFitToPage(true);
    sheet.setDisplayGridlines(false);
    sheet.setDefaultColumnWidth(23);
    sheet.setDefaultRowHeight((short) 290);

    addTitleToSheet(sheet);
    //currRow++;
    addHeaders(sheet);
    //initDataStyle();
}

@Override
public void write(List<? extends ViewReportSalesAnalyticsDTO> items) throws Exception {
    log.info("Analytic write, number of elements - {} ", items.size());

    Sheet sheet = workbook.getSheetAt(1);
    SimpleDateFormat spf = new SimpleDateFormat(PogEngineBatchConstants.DATE_FORMAT_REPORT);
    Row row = null;

    for (ViewReportSalesAnalyticsDTO data : items) {
        int i = 0;
        currRow++;

        row = sheet.createRow(currRow);
        createStringCell(row, data.getProductCode(), i++);
        createStringCell(row, data.getProductDesc(), i++);
        createStringCell(row, PogEngineBatchUtils.decodeFlagFactory(data.getFlagManufacturer()), i++);

        for (String headerKey : dynamicHeaders) {
            createStringCell(row, data.getSellingJson().get(headerKey) == null ? "OK" : //TODO always OK?
                    data.getSellingJson().get(headerKey), i++);
        }
        createStringCell(row, data.getMonitoringOutcome(), i++);

        for (String headerKey : dynamicHeaders) {
            createStringCell(row, data.getMonitorJson().get(headerKey) == null ? "OK" : //TODO always OK?
                    data.getMonitorJson().get(headerKey), i++);
        }
    }
}

@AfterStep
public void afterStep(StepExecution stepExecution) throws IOException {
    FileOutputStream fos = new FileOutputStream(outputFilename);
    log.info("ViewReportSalesAnalyticsExcelWriter afterStep, write records to file");
    workbook.write(fos);
    fos.close();
    workbook.dispose();
    workbook.close();
}
在 workbook.write(fos); 期间的 afterStep 中我们正在写大约 500.000 条记录,我们得到 GC 开销内存 .为了避免这个问题,我们将这个 VM 参数 -Xmx8192m 放在批处理启动器(sh)中,这需要很长时间,并且使用来自 VM(16 GB 和 8 CORE)的大量内存来结束进程并写入文件(约 110 MB)。
我们可以在代码中做些什么来不与内存重叠(因为数据会在 future 的执行中增长)?
为了不一次写入 500.000 条记录,有什么解决方案可以改变 workbook.write() 的行为?也许我们可以在这个过程中 split ?

最佳答案

该代码与 https://keyholesoftware.com/2012/11/12/generating-large-excel-files-using-spring-batch-part-three/ 非常相似.这种方法确实 不是 遵循 Spring Batch 提出的面向 block 的处理模型。问题是 Writer.write仅在内存中创建单元格,工作簿仅在 AfterStep 中写入磁盘打回来。显然,使用这种方法,所有项目都保存在内存中,直到整个步骤完成,然后才将它们写入磁盘。
项目应该在每个 block 之后而不是在整个步骤之后写入磁盘。

关于java - Apache POI SXSSFWorkbook 在 XLSX 文件内存问题中写入大约 500.000,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66052083/

相关文章:

java - 错误 : Unsupported method: AndroidProject. getVariantNames()

java - 在 Struts2 JSP 中获取 Map 的值

java - Java从Excel中获取文本框的值

java - 正则表达式挂起程序(100% CPU 使用率)

vba - 列格式转换为数字,但显示为文本

vsto - Excel VSTO排序对话框C#

arrays - Excel:如何否定 bool 数组?

java - 如何在 Apache POI 数据透视表报告过滤器中设置默认值

java - 我使用 apache poi 创建 Excel 文件。但是当我打开 Excel 文件时,它说格式错误的文件需要恢复

java - 动态显示我的本地计算机位置的图像