Apache POI WorkbookFactory.create抛出java.lang.OutOfMemoryError:Java堆空间

时间:2014-09-11 20:54:47

标签: java apache-poi spreadsheet google-app-engine

我的问题非常简单。我想验证最大50MB的文件,以便在App Engine中进行正确的格式化。

现在提出了许多重大挑战。第一个是Apache XLS / XLSX POI API。当我在验证之前将20MB的文件数据加载到内存本地时,它会抛出:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2271)
    at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113)
    at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140)
    at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource$FakeZipEntry.<init>(ZipInputStreamZipEntrySource.java:128)
    at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource.<init>(ZipInputStreamZipEntrySource.java:55)
    at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:84)
    at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:272)
    at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:79)

我需要打开最大20到25MB的电子表格。如果可能的话,50将是一个不错的伸展目标。我们在一张纸上谈论了数十万行数据。

现在,我的传统代码将整个文件加载到内存中,并立即使应用程序引擎实例的堆崩溃。这是我的传统代码:

    public ErrorLog validateWorkbook(inputWorkbook)
    {
        int sheetCount = inputWorkbook.getNumberOfSheets();
        for (int x = 0; x< sheetCount; x++)
        {
            Sheet currentSheet = inputWorkbook.getSheetAt(x);
            Iterator<Row> rowIterator = currentSheet.rowIterator();
            while(rowIterator.hasNext())
            {
                Iterator<Cell> cellIterator = rowIterator.next().cellIterator();
                while(cellIterator.hasNext())
                {
                    Cell currentCell = cellIterator.next();
                    boolean success = validateCellContents(currentCell);
                    if(!success)
                        ErrorLog.appendError(new Error()); // detailed user error explicitly defining error location, cell value, and recommended steps to fix
                }
            }
        }
        return ErrorLog;
    }

现在,每次遇到一个单元格时,都会有基于事件的方法来处理actionlistener。但是虚拟代码here引用了:

ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(container); 

我在调试器中检查了这个对象,它包含当前工作表中的每个唯一字符串引用。这基本上正是我正在努力避免的。它正在分配一大块内存来预先将每个值存储在内存中。理想的解决方案采用输入字节流,并在遍历文件时对字符串进行解码,以减少内存占用。

因为字符串表肯定会在内存中占用大量空间。我正在处理150,000到300,000个订单项电子表格

现在quick guide提到你可以使用文件 InputStream ,如果你使用文件,输入将被缓冲。这里的问题是App Engine和Blob Store Service,不知道文件对象,只返回InputStreams(据我所知)。

此外,另一个事件驱动模型Default Handler似乎没有任何关于其操作发生时调用的接口定义方法中每个值的列或行的概念(并且它也预先分配整个共享字符串表。)

这里没有想法!要尝试为此提供赏金。至少一个具体的“不这是不可能的”就足够了,然后我就可以开始研究解决方法,但我觉得我并没有像我那样使用庞大的API。

1 个答案:

答案 0 :(得分:1)

提倡,

可以这样做,  但是你必须要有创意来解决GAE的一些限制。

首先,App引擎前端实例的请求限制为1分钟,因此如果您要处理大小为50 MB的文件,您将被迫使用task queues或使用“{{3} “逃避时间限制。

第二,记忆。在这里你有两个选择,Manual/basic scaling module你可以更好地控制你的实例内存,这是朝着正确方向迈出的一步,但它不会很好地扩展。

我一直在你的情况下,我最终使用Using modules + Google Drive APIGoogle Spreadheets API,具体取决于要求。使用其中任何一种替代方案,我上传了excel文件,以便我可以使用队列离线批处理它们。