如何使用Apache POI加载大型xlsx文件?

Xen*_*oRo 41 java memory out-of-memory apache-poi xssf

我有一个大的.xlsx文件(141 MB,包含293413行,每行62列)我需要在其中执行一些操作.

我在加载此文件(OutOfMemoryError)时遇到问题,因为POI在XSSF(xlsx)工作簿上占用大量内存.

这个问题类似,提出的解决方案是增加VM的分配/最大内存.

它似乎适用于那种文件大小(9MB),但对我来说,即使分配所有可用的系统内存,它也只是不起作用.(嗯,考虑到文件大于15倍,这并不奇怪)

我想知道是否有任何方法以不占用所有内存的方式加载工作簿,但是,没有基于(进入)XSSF的底层XML进行处理.(换句话说,维护清教徒POI解决方案)

如果没有强硬的话,欢迎你说("没有."),并指出了解决"XML"问题的方法.

mon*_*jbl 52

我在网络服务器环境中处于类似情况.上传的典型大小约为150k行,从单个请求中消耗大量内存并不是一件好事.Apache POI Streaming API适用于此,但它需要重新设计读取逻辑.我已经有一堆使用标准API的读取逻辑,我不想重做,所以我写了这个:https://github.com/monitorjbl/excel-streaming-reader

它并不完全是标准XSSFWorkbook类的替代品,但如果你只是遍历行,它的行为类似:

import com.monitorjbl.xlsx.StreamingReader;

InputStream is = new FileInputStream(new File("/path/to/workbook.xlsx"));
StreamingReader reader = StreamingReader.builder()
        .rowCacheSize(100)    // number of rows to keep in memory (defaults to 10)
        .bufferSize(4096)     // buffer size to use when reading InputStream to file (defaults to 1024)
        .sheetIndex(0)        // index of sheet to use (defaults to 0)
        .read(is);            // InputStream or File for XLSX file (required)

for (Row r : reader) {
  for (Cell c : r) {
    System.out.println(c.getStringCellValue());
  }
}     
Run Code Online (Sandbox Code Playgroud)

使用它有一些注意事项; 由于XLSX表的结构方式,并非所有数据都在流的当前窗口中可用.但是,如果您只是尝试从单元格中读取简单数据,那么它的效果非常好.

  • 这个解决方案很棒。在 git 页面上定义了一个新用法:`InputStream is = new FileInputStream(new File("/path/to/workbook.xlsx")); Workbook workbook = StreamingReader.builder().rowCacheSize(100).bufferSize(4096).sheetIndex(0).open(is);`就像它被加载到内存中一样。 (2认同)

rjd*_*olb 11

可以通过使用File而不是Stream来改进内存使用.(最好使用流式API,但Streaming API有局限性,请参阅http://poi.apache.org/spreadsheet/index.html)

而不是

Workbook workbook = WorkbookFactory.create(inputStream);
Run Code Online (Sandbox Code Playgroud)

Workbook workbook = WorkbookFactory.create(new File("yourfile.xlsx"));
Run Code Online (Sandbox Code Playgroud)

这是根据:http://poi.apache.org/spreadsheet/quick-guide.html#FileInputStream

文件与InputStreams

"当打开工作簿时,无论是.xls HSSFWorkbook还是.xlsx XSSFWorkbook,都可以从File或InputStream加载工作簿.使用File对象可以降低内存消耗,而InputStream需要更多内存,因为它有缓冲整个文件."


Gag*_*arr 9

Apache POI,HSSF和XSSF中的Excel支持支持3种不同的模式.

一个是完整的DOM-Like内存"UserModel",它支持读写.使用通用的SS(SpreadSheet)接口,您可以基本透明地编写HSSF(.xls)和XSSF(.xlsx).但是,它需要大量的内存.

POI还支持流式只读方式来处理文件EventModel.这比UserModel低得多,让您非常接近文件格式.对于HSSF(.xls),您可以获得一系列记录,并可选择一些处理它们的帮助(缺少单元格,格式跟踪等).对于XSSF(.xlsx),您可以从文件的不同部分获取SAX事件流,帮助获取文件的正确部分,并且还可以轻松处理文件的常见但小部分.

仅对于XSSF(.xlsx),POI还支持只写流式写入,适用于低级别但低内存写入.它主要只是支持新文件(某些类型的附加是可能的).没有HSSF等价物,并且由于许多记录中的来回字节偏移和索引偏移,所以很难做到......

对于您的具体情况,如您的澄清评论中所述,我认为您将要使用XSSF EventModel代码.见的POI文件上手,然后尝试寻找 3 中的POI和提卡它使用它的更多细节.


Alf*_*avo 7

POI现在包含这些案例的API.SXSSF http://poi.apache.org/spreadsheet/index.html 因此它可以让你处理这些文件时,它不会加载在内存中的所有.

注意:我已经读过SXSSF作为写入API.加载应该使用XSSF完成而无需输入流文件(以避免在内存中完全加载它)

  • 我已经读过SXSSF作为一个写作API.加载应该使用XSSF完成而无需输入流文件(以避免在内存中完全加载它) (4认同)
  • SXSSF不用于读取,它仅用于写入文件 (3认同)

Yan*_*ski 6

检查这个帖子。我展示了如何使用 SAX 解析器来处理 XLSX 文件。

/sf/answers/3147830661/

简而言之,我org.xml.sax.helpers.DefaultHandler为 XLSX filez扩展了 whih 处理 XML 结构。t 是事件解析器 - SAX。

class SheetHandler extends DefaultHandler {

    private static final String ROW_EVENT = "row";
    private static final String CELL_EVENT = "c";

    private SharedStringsTable sst;
    private String lastContents;
    private boolean nextIsString;

    private List<String> cellCache = new LinkedList<>();
    private List<String[]> rowCache = new LinkedList<>();

    private SheetHandler(SharedStringsTable sst) {
        this.sst = sst;
    }

    public void startElement(String uri, String localName, String name,
                             Attributes attributes) throws SAXException {
        // c => cell
        if (CELL_EVENT.equals(name)) {
            String cellType = attributes.getValue("t");
            if(cellType != null && cellType.equals("s")) {
                nextIsString = true;
            } else {
                nextIsString = false;
            }
        } else if (ROW_EVENT.equals(name)) {
            if (!cellCache.isEmpty()) {
                rowCache.add(cellCache.toArray(new String[cellCache.size()]));
            }
            cellCache.clear();
        }

        // Clear contents cache
        lastContents = "";
    }

    public void endElement(String uri, String localName, String name)
            throws SAXException {
        // Process the last contents as required.
        // Do now, as characters() may be called more than once
        if(nextIsString) {
            int idx = Integer.parseInt(lastContents);
            lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
            nextIsString = false;
        }

        // v => contents of a cell
        // Output after we've seen the string contents
        if(name.equals("v")) {
            cellCache.add(lastContents);
        }
    }

    public void characters(char[] ch, int start, int length)
            throws SAXException {
        lastContents += new String(ch, start, length);
    }

    public List<String[]> getRowCache() {
        return rowCache;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我解析 XML presending XLSX 文件

private List<String []> processFirstSheet(String filename) throws Exception {
    OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
    XSSFReader r = new XSSFReader(pkg);
    SharedStringsTable sst = r.getSharedStringsTable();

    SheetHandler handler = new SheetHandler(sst);
    XMLReader parser = fetchSheetParser(handler);
    Iterator<InputStream> sheetIterator = r.getSheetsData();

    if (!sheetIterator.hasNext()) {
        return Collections.emptyList();
    }

    InputStream sheetInputStream = sheetIterator.next();
    BufferedInputStream bisSheet = new BufferedInputStream(sheetInputStream);
    InputSource sheetSource = new InputSource(bisSheet);
    parser.parse(sheetSource);
    List<String []> res = handler.getRowCache();
    bisSheet.close();
    return res;
}

public XMLReader fetchSheetParser(ContentHandler handler) throws SAXException {
    XMLReader parser = new SAXParser();
    parser.setContentHandler(handler);
    return parser;
}
Run Code Online (Sandbox Code Playgroud)

  • 你的 fetchSheetParser 在哪里? (2认同)