POI Event API如何从Excel读取数据,为什么使用较少的RAM?

时间:2019-01-30 12:18:40

标签: events apache-poi

我目前正在写我的学士论文,并且正在使用来自Apache的POI Event API。简而言之,我的工作是关于从Excel读取数据的更有效方法。

开发人员一再问我,事件API到底是什么意思。不幸的是,我在Apache页面上找不到关于基本原理的任何信息。

以下代码,我如何使用POI事件API(这来自XSSF和SAX的Apache示例):

import java.io.InputStream;
import java.util.Iterator;

import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;

public class ExampleEventUserModel {
    public void processOneSheet(String filename) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader( pkg );
        SharedStringsTable sst = r.getSharedStringsTable();

        XMLReader parser = fetchSheetParser(sst);

        // To look up the Sheet Name / Sheet Order / rID,
        //  you need to process the core Workbook stream.
        // Normally it's of the form rId# or rSheet#
        InputStream sheet2 = r.getSheet("rId2");
        InputSource sheetSource = new InputSource(sheet2);
        parser.parse(sheetSource);
        sheet2.close();
    }

    public void processAllSheets(String filename) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader( pkg );
        SharedStringsTable sst = r.getSharedStringsTable();

        XMLReader parser = fetchSheetParser(sst);

        Iterator<InputStream> sheets = r.getSheetsData();
        while(sheets.hasNext()) {
            System.out.println("Processing new sheet:\n");
            InputStream sheet = sheets.next();
            InputSource sheetSource = new InputSource(sheet);
            parser.parse(sheetSource);
            sheet.close();
            System.out.println("");
        }
    }

    public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException, ParserConfigurationException {
        XMLReader parser = SAXHelper.newXMLReader();
        ContentHandler handler = new SheetHandler(sst);
        parser.setContentHandler(handler);
        return parser;
    }

    /**
     * See org.xml.sax.helpers.DefaultHandler javadocs
     */
    private static class SheetHandler extends DefaultHandler {
        private SharedStringsTable sst;
        private String lastContents;
        private boolean nextIsString;

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

        public void startElement(String uri, String localName, String name,
                                 Attributes attributes) throws SAXException {
            // c => cell
            if(name.equals("c")) {
                // Print the cell reference
                System.out.print(attributes.getValue("r") + " - ");
                // Figure out if the value is an index in the SST
                String cellType = attributes.getValue("t");
                if(cellType != null && cellType.equals("s")) {
                    nextIsString = true;
                } else {
                    nextIsString = false;
                }
            }
            // 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 = sst.getItemAt(idx).getString();
                nextIsString = false;
            }

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

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

    public static void main(String[] args) throws Exception {
        ExampleEventUserModel example = new ExampleEventUserModel();
        example.processOneSheet(args[0]);
        example.processAllSheets(args[0]);
    }
}

有人可以向我解释Event API的工作原理吗?它与基于事件的体系结构相同还是其他?

1 个答案:

答案 0 :(得分:2)

*.xlsx文件(存储在Office Open XML中,Excel作为apache poi处理的文件)是包含数据的XSSF存档在目录结构中的ZIP个文件中。因此,我们可以解压缩XML文件,然后直接从*.xlsx文件中获取数据。

其中有XML,其中包含所有字符串单元格值。并且/xl/sharedStrings.xml描述了工作簿的结构。并且/xl/workbook.xml正在存储工作表的数据。并且/xl/worksheets/sheet1.xml, /xl/worksheets/sheet2.xml, ...具有工作表中所有单元格的样式设置。

默认情况下,在创建/xl/styles.xml文件时,XSSFWorkbook文件的所有那些部分将成为对象表示形式,分别为*.xlsxXSSFWorkbookXSSFSheet,{{1 }},...以及内存中XSSFRow的其他对象。

要了解一下XSSFCellorg.apache.poi.xssf.*.*XSSFSheet的内存消耗情况,可以查看源代码。这些对象中的每一个都包含多个XSSFRowXSSFCell作为内部成员,当然也包含多个方法。现在,想象一张有成千上万行的工作表,每行包含多达数百个单元格。这些行和单元格中的每一个将由内存中的ListMap表示。这不能成为XSSFRow的指责,因为如果需要使用这些对象,则这些对象是必需的。但是,如果实际上只需要从XSSFCell表中获取内容,则这些对象并不是全部必需的。这就是XSSF and SAX (Event API)方法的原因。

因此,如果只需要从工作表中读取数据,则可以简单地解析所有apache poi文件中的Excel,而无需为每张工作表,每一行和每一行创建消耗内存的对象这些表中的单元格。

在基于事件的模式下解析XML意味着该代码自上而下遍历/xl/worksheets/sheet[n].xml并定义了回调方法,如果代码检测到元素的开始,元素的结束,则调用该方法或元素中的字符内容。然后,适当的回调方法处理在元素的开始,结束或使用字符内容时要做什么。因此,读取XML文件仅意味着一次自上而下地浏览文件,处理事件(元素的开始,结束,字符内容)并能够从中获取所有需要的内容。因此,将内存消耗减少为存储从XML获得的文本数据。

XSSF and SAX (Event API)使用类XML为此扩展了DefaultHandler

但是,如果我们已经处于获取基础XML数据并进行处理的这一级别,那么我们也可以再退一步。本机SheetHandler能够处理XML并解析Java。因此,我们甚至根本不需要其他库。请参阅how read excel file having more than 100000 row in java?,其中已显示了此内容。我的代码使用Package javax.xml.stream,该代码还提供基于事件的ZIP,但不使用回调,而是使用线性代码。也许这段代码更容易理解,因为它是一个整体。

为检测数字格式是否为日期格式,因此格式化的单元格包含日期/时间值,使用了一个单独的XMLXMLEventReader。这样做是为了简化代码。当然,即使是此类,我们也可以编写自己的代码。