Aspose cells for java 解析包含大量带状行样式的Excel文件时,构造Workbook对象抛出OOM异常

在加载一个带有大量“带状行样式”(banded rows)的Excel文件时,调用 Workbook 构造方法时触发 java.lang.OutOfMemoryError ,导致程序崩溃。具体位置为 Workbook wbk = new Workbook(formatFile.getLocalFile());

JDK:17
aspose-cells:21.11 已试过26.4版本 该问题仍存在
JVM堆内存设置:-Xms32g -Xmx32g

相关代码片段:
public CellsFormatAdapter(FormatFile formatFile) throws Exception {
this.formatFile = formatFile;
Workbook wbk = new Workbook(formatFile.getLocalFile());
// 执行函数计算
calculateFormula(wbk);
// 检查 Sheet 页中是否存在打印区域设置
checkAndClearPrintArea(wbk.getWorksheets());
}

Excel内容示例如下图:
image.png (22.0 KB)
使用新版本异常信息如下图:
image.png (80.9 KB)

您好,

我们已经收到了您关于加载包含大量带状行样式的 Excel 文件时出现 java.lang.OutOfMemoryError 的反馈。

我们尝试根据您提供的代码片段和截图进行复现,但在常规测试中未能重现该 OOM 异常。由于 32GB 的堆内存(-Xmx32g)对于大多数 Excel 处理场景应当是充足的,该问题可能与特定文件内部的样式堆积或结构损坏有关。

为了进一步定位问题原因,请提供以下信息:

  1. 能够触发该异常的 Excel 源文件。仅凭截图我们无法分析文件的内部 XML 结构。
  2. 如果该文件包含敏感数据,您可以将其删除,只要确保处理后的文件仍能复现该 OOM 问题即可。

收到您的样本文件后,我们将立即进行深入测试。

您好,
异常的文件如下:
error file.7z (830.4 KB)

@Whataya_Want_From_Me

您好,

感谢您提供用于复现问题的示例文件。

我们已成功接收到该压缩包,并将其关联至正在进行的调查流程中。我们的开发团队将使用此文件对解析带状行样式(banded rows)时的内存占用情况进行深入分析,以定位导致 OutOfMemoryError 的具体原因。

一旦有任何进展或修复方案,我们会第一时间在此贴中通知您。感谢您的耐心配合。

@Whataya_Want_From_Me ,

您的源文件“error file.xlsx”用Aspose.Cells for Java 26.4/26.5可以成功加载,仅占用几十MB的内存。
请提供一段可运行的代码,以便我们重现您遇到的问题。

    public static void test() throws Exception
    {
        System.out.println(CellsHelper.getVersion());

        // =====================================================
        // Load workbook
        // =====================================================
        
        long memBeforeLoad = usedMemory();
        long startLoad = System.nanoTime();
        
        Workbook wb = new Workbook("error file.xlsx");

        long endLoad = System.nanoTime();
        long memAfterLoad = usedMemory();

        printStats(
                "Workbook Load",
                startLoad,
                endLoad,
                memBeforeLoad,
                memAfterLoad);

        gcPause();

        // =====================================================
        // Calculate formulas
        // =====================================================

        long memBeforeCalc = usedMemory();
        long startCalc = System.nanoTime();

        wb.calculateFormula();

        long endCalc = System.nanoTime();
        long memAfterCalc = usedMemory();


        printStats(
                "calculateFormula()",
                startCalc,
                endCalc,
                memBeforeCalc,
                memAfterCalc);

        wb.dispose();
    }

    private static long usedMemory() {
        Runtime rt = Runtime.getRuntime();
        return rt.totalMemory() - rt.freeMemory();
    }

    private static void gcPause() throws Exception {
        System.gc();
        Thread.sleep(1000);
    }

    private static void printStats(
            String title,
            long startTime,
            long endTime,
            long memBefore,
            long memAfter) {

        double timeMs = (endTime - startTime) / 1_000_000.0;

        long memDiff = memAfter - memBefore;

        System.out.println("========== " + title + " ==========");
        System.out.printf("Time Cost : %.2f ms%n", timeMs);
        System.out.printf("Memory Before : %.2f MB%n",
                memBefore / 1024.0 / 1024.0);
        System.out.printf("Memory After  : %.2f MB%n",
                memAfter / 1024.0 / 1024.0);
        System.out.printf("Memory Delta  : %.2f MB%n",
                memDiff / 1024.0 / 1024.0);
        System.out.println();
    }

测试类代码如下:
JobExecutorTest.7z (1.4 KB)

@Whataya_Want_From_Me

在Excel打印预览中,工作表“底数据”有超过12万页。你的代码设置了OnePagePerSheet,一个工作表的所有内容都会输出到的pdf的一页中。把12万页输出到一个pdf页是几乎不可能的,导致了OOM异常。

@peyton.xu
但是有数据的行数仅有9000多行,其余行数都是带状行样式,并未有数据,如何仅读取有数据的部分呢?

@Whataya_Want_From_Me

工作表“底数据”,如果只要打印有数据的部分,一般情况下,都是在工作表上设置合适的打印区域。
不过你的代码有清理打印区域的逻辑,所以只能修改源文件了,删除掉没有数据的行。

另外,一个工作表如果有超过几百/上千的页面,是不建议应用OnePagePerSheet选项的。

@Whataya_Want_From_Me
工作表“底数据”中添加了一个整列的Table。Excel会打印所有有数值或者样式的单元格,Aspose.Cells 现在和Excel行为一致。 如果你不希望打印这些多余的行,你可以如下操作:
1,不要设置整列的Table, 重新设置Table只包含A1:AE9157
2, 设置打印区域,可以在Excel中设置A1:AE9157为打印区域
也可以使用代码:

public static void main(String[] args) throws Exception {
        // 1. 加载许可证(如果有),无许可证会有水印
        // License license = new License();
        // license.setLicense("Aspose.Cells.lic");

        // 2. 加载 Excel 文件
        Workbook workbook = new Workbook("test.xlsx");
        Worksheet worksheet = workbook.getWorksheets().get("底数据"); 

        // 3. 获取工作表中**有数据的最大行、最大列**(关键)
        Cells cells = worksheet.getCells();
        int maxDataRow = cells.getMaxDataRow();       // 最大数据行索引(从0开始)
        int maxDataColumn = cells.getMaxDataColumn(); // 最大数据列索引(从0开始)


        // 4. 自动设置打印区域 = 从 A1 到 最大数据单元格
        // 格式:$A$1:$最后列$最后行
        String printArea = "$A$1:$" + CellsHelper.cellIndexToName(maxDataRow, maxDataColumn);
        worksheet.getPageSetup().setPrintArea(printArea);

        System.out.println("已设置打印区域:" + printArea);

        // 5. 保存文件
        workbook.save("output_with_print_area.pdf");
        System.out.println("文件已保存!");
    }

@simon.zhao

您提供的代码中,在我这边编译报错,Cells.cellIndexToName()没有这个方法,您这边用的com.aspose.cells是哪个版本?
Cells.cellIndexToName(0, maxDataColumn));

@Whataya_Want_From_Me

您好,

关于您提到的 Cells.cellIndexToName() 编译错误问题,这是因为在 Aspose.Cells for Java 中,该方法属于静态工具方法。

请确保您使用的是 CellsHelper.cellIndexToName(int row, int column) 而不是直接通过 Cells 实例调用。正确的调用方式如下:

// 正确的 API 调用方式
String cellName = CellsHelper.cellIndexToName(row, column);

该方法在您目前尝试的 21.11 及 24.6 等版本中均是稳定可用的。关于您反馈的 OOM 异常,我们的开发团队正在结合相关逻辑进行内部排查,一旦有进一步的调查结果或优化进展,我们会立即在此贴中告知您。