Как API событий POI считывает данные из Excel и почему он использует меньше оперативной памяти? - PullRequest
0 голосов
/ 30 января 2019

Сейчас я пишу дипломную работу и использую API событий POI от Apache.Короче говоря, моя работа посвящена более эффективному способу чтения данных из Excel.

Меня снова и снова спрашивают разработчики, как именно это подразумевается под Event API.К сожалению, я не нашел ничего на странице Apache об основном принципе.

Следующий код, как я использую API событий POI (это из примера Apache для XSSF и SAX):

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]);
    }
}

Может кто-нибудь объяснить мне, как работает API событий?Это то же самое, что архитектура, основанная на событиях, или это что-то еще?

1 Ответ

0 голосов
/ 30 января 2019

Файл *.xlsx, который Excel хранится в Office Open XML и который 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 все эти части файла *.xlsx станут объектными представлениями как XSSFWorkbook, XSSFSheet, XSSFRow, XSSFCell, ... и другие объекты размером org.apache.poi.xssf.*.* в памяти.

Чтобы получить представление о том, как потребляют память XSSFSheet, XSSFRow и XSSFCellПосмотрите на источники будет хорошо.Каждый из этих объектов содержит несколько List s и Map s в качестве внутренних членов и, конечно, также несколько методов.Теперь представьте лист с сотнями тысяч строк, каждый из которых содержит до сотен ячеек.Каждая из этих строк и ячеек будет представлена ​​в памяти XSSFRow или XSSFCell.Это не может быть обвинением в apache poi, потому что эти объекты необходимы, если необходима работа с этими объектами.Но если на самом деле нужно только извлечь содержимое из листа Excel, то эти объекты не являются необходимыми.Вот почему XSSF и SAX (Event API) подходят.

Поэтому, если нужно только читать данные с листов, можно просто проанализировать XML всех файлов /xl/worksheets/sheet[n].xml безнеобходимость создания объектов, потребляющих память, для каждого листа и для каждой строки и для каждой ячейки в этих листах.

Синтаксический анализ XML в режиме на основе событий означает, что код идет сверху вниз через XML и имеет определенные методы обратного вызова, которые вызываются, если код обнаруживает начало элемента, конец элемента илисодержание символа в элементе.Затем соответствующие методы обратного вызова обрабатывают, что делать в начале, в конце или с символьным содержимым элемента.Таким образом, чтение файла XML означает только однократную пробежку по файлу сверху вниз, обработку событий (начало, конец, символьное содержимое элемента) и возможность извлечь из него весь необходимый контент.Таким образом, потребление памяти сокращается до хранения текстовых данных, полученных из XML.

XSSF и SAX (Event API) использует класс SheetHandler, который расширяет DefaultHandler для этого.

Но если мы уже находимся на этом уровне, где мы получаем базовые данные XML и обрабатываем их, то мы могли бы также сделать еще один шаг назад.Родной Java способен обрабатывать ZIP и анализировать XML.Поэтому нам даже не понадобятся дополнительные библиотеки.См. , как читать файл Excel, имеющий более 100000 строк в Java? , где я это показал.Мой код использует пакет javax.xml.stream , который также обеспечивает использование событий на основе XMLEventReader, но не с использованием обратных вызовов, а с использованием линейного кода.Возможно, этот код проще для понимания, потому что он все в одном.

Для определения того, является ли числовой формат форматом даты, и поэтому отформатированная ячейка содержит значение даты / времени, один единственный apache poi класс org.apache.poi.ss.usermodel.DateUtil используется.Это сделано для упрощения кода.Конечно, даже этот класс мы могли бы закодировать сами.

...