如何加载与Apache POI大XLSX文件?(How to load a large xlsx f

2019-06-17 17:02发布

我有一个大.xlsx文件(141 MB,将含有293413行,每行62列)我需要内执行一些操作。

我有装载问题这个文件( OutOfMemoryError ),作为POI对XSSF(XLSX)工作簿大容量内存。

这太问题是相似的,而提出的解决方案是增加虚拟机的分配/最大内存。

这似乎为那种文件大小(9MB)的工作,但对我来说,它只是简单地不即使分配所有可用的系统内存工作。 (好吧,这是毫不奇怪考虑该文件是超过15倍)

我想知道是否有任何方式来加载在某种程度上,它不会消耗所有的内存,然而该工作簿,而不做基于处理(进入)的XSSF的基础XML。 (换句话说,保持清教徒POI溶液)

如果没有坚韧,欢迎您说它(“没有。”),并指向我的方式为“XML”的解决方案。

Answer 1:

我是在一个web服务器环境类似的情况。 在上传的典型大小为150K〜行,它不会一直不错,从单个请求消耗一吨的内存。 在Apache POI流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());
  }
}     

还有一些注意事项使用它; 由于方式XLSX片材结构,不是所有的数据是在流的当前窗口中可用。 不过,如果你只是想从细胞中读取简单的数据出来,它工作得很好了点。



Answer 2:

在内存使用量的提高可以通过使用一个文件,而不是流来完成。 (这是更好地使用流API,但流API的有限制,请参见http://poi.apache.org/spreadsheet/index.html )

因此,而不是

Workbook workbook = WorkbookFactory.create(inputStream);

Workbook workbook = WorkbookFactory.create(new File("yourfile.xlsx"));

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

文件VS InputStreams

“当打开工作簿,无论是一个的.xls HSSFWorkbook或.XLSX XSSFWorkbook,工作簿可以从文件或一个InputStream装载。使用文件对象允许更低的内存消耗,而一个InputStream需要更多的存储器,因为它有缓冲整个文件“。



Answer 3:

在Apache的POI,HSSF和XSSF,该支持Excel支持3种不同的模式。

一个是一个完整的,类似DOM的内存“的usermodel”,它同时支持读取和写入。 使用普通党卫军(电子表格)的接口,你可以为这两个HSSF(.xls的)和XSSF(.xlsx)格式基本上是透明的代码。 但是,它需要大量的内存。

POI还支持流媒体只读方式处理的文件,该EventModel。 这是级别低比的usermodel得多,并让你非常接近的文件格式。 对于HSSF(.xls的),你得到的记录流,以及可选一些帮助处理这些(缺细胞,格式跟踪等)。 对于XSSF格式(.xlsx)你从文件的不同部分SAX事件流,提供帮助,以获取该文件的右侧部分和文件的共同但有小块的也容易处理。

对于XSSF(.XLSX)只,POI还支持一个只写流写入,适用于低的水平,但低存储器的写入。 它在很大程度上只是支持虽然新文件(某些种类的追加是可能的)。 没有HSSF等价的,因背部和往复字节偏移和索引偏移量多条记录这将是非常难以做到...

针对您的特殊情况下,如在澄清意见描述,我想你会想使用XSSF EventModel代码。 见的POI文件上手,然后尝试寻找这 3 类中的POI和提卡它使用它的更多细节。



Answer 4:

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

注:我已阅读,SXSSF工作作为书写API。 装载应使用不XSSF的inputstream'ing文件来完成(以避免在内存满负荷)



Answer 5:

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

https://stackoverflow.com/a/44969009/4587961

总之,我扩展org.xml.sax.helpers.DefaultHandler whih处理XML结构XLSX filez。 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;
    }
}

然后我解析XML预送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;
}


Answer 6:

您可以使用SXXSF而不是使用HSSF。 我能生成20万行出类拔萃。



Answer 7:

基于monitorjbl的回答和测试套件从POI探讨,以下为我工作的多页文件的xlsx与200K记录(大小> 50 MB):

import com.monitorjbl.xlsx.StreamingReader;
. . .
try (
        InputStream is = new FileInputStream(new File("sample.xlsx"));
        Workbook workbook = StreamingReader.builder().open(is);
) {
    DataFormatter dataFormatter = new DataFormatter();
    for (Sheet sheet : workbook) {
        System.out.println("Processing sheet: " + sheet.getSheetName());
        for (Row row : sheet) {
            for (Cell cell : row) {
                String value = dataFormatter.formatCellValue(cell);
            }
        }
    }
}


文章来源: How to load a large xlsx file with Apache POI?