Я разрабатываю приложение для Kindle Fire, которое извлекает файл .xlsx из Dropbox и использует Apache POI для анализа данных в базе данных SQLite (одна таблица с 10 свойствами - я разобью ее на несколько таблиц, как только смогу получить парсинг рабочий). Размер файла чуть более 2 МБ (~ 28 000 строк, 10 столбцов в каждой), поэтому, когда я изначально начал тестирование на физическом устройстве (эмулятор работал нормально, но был очень медленным), я столкнулся с OutOfMemoryErrors. Я много копал и обнаружил, что могу реализовать SAX, чтобы уменьшить объем используемой памяти. Однако я не совсем уверен, как поместить все данные в мою таблицу - на основе примера кода, который я просмотрел, каждая ячейка (по крайней мере, из того, что я могу сказать) оценивается индивидуально, поэтому я не могу сделать один запрос на итерацию строки. Другая проблема, с которой я сталкиваюсь, - это столбец чисел (цен), который выводится на консоль (через Debug.print ()) дважды, и я не могу понять, почему. Я почти в своем уме - я потратил несколько дней, исправляя различные проблемы с Dropbox и POI, но этот меня поставил в тупик. Я пока использую эти три в качестве шаблонов / руководств (в основном последние):

http://www.saxproject.org/quickstart.html

https://github.com/apache/poi/blob/230453d76e6e912dfa22088544488a0a6baf83a2/src/ooxml/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java

https://dzone.com/articles/introduction-to-apache-poi-library

И я просмотрел эти (среди нескольких других):

Как читать файл XLSX размером> 40 МБ

http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/examples/FromHowTo.java

http://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api

Чтение таблицы Excel с использованием POI XSSF и SAX (Event API)

https://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java

Это довольно простое приложение, поэтому мне не нужно ничего особенного - просто нужно запустить на этом этапе. Итак, я предполагаю, что мои вопросы таковы: является ли SAX лучшим способом избежать проблем с памятью? Если да, как я могу реализовать его для точного анализа каждой строки в моей базе данных? Если нет, то в каком направлении мне двигаться? Вот что у меня есть для моего класса синтаксического анализа (для тестирования используются runningTime и isParsing):

import android.content.Context;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
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 org.xml.sax.helpers.XMLReaderFactory;


public class ExcelParser {
    public static boolean isParsing = false;
    private DBHelper dbHelper;
    private File dropboxFile;


    // purpose: parameterized constructor
    // parameters: FileInputStream inputStream
    // returns: nothing
    public ExcelParser(Context context, File dropboxFile) {
        this.dropboxFile = dropboxFile;
        dbHelper = new DBHelper(context);
    }// end ExcelParser(FileInputStream inputStream)


    // purpose: parses the inputStream (.xlsx) into a list of Product objects
    // parameters: none
    // returns: void
    public void parseToDB() {
        Long runningTime = System.currentTimeMillis();

        isParsing = true;
        Debug.print(this.getClass().getSimpleName(), "----- STARTING -----");

        try {
            OPCPackage pkg = OPCPackage.open(dropboxFile);
            XSSFReader xssfReader = new XSSFReader(pkg);
            SharedStringsTable sharedStringsTable = xssfReader.getSharedStringsTable();
            XMLReader parser = getSheetParser(sharedStringsTable);

            Iterator<InputStream> sheets = xssfReader.getSheetsData();
            Debug.print(this.getClass().getSimpleName(), "sheet processing");
            while(sheets.hasNext()) {
                Debug.print(this.getClass().getSimpleName(), "Processing sheet");

                InputStream sheet = sheets.next();
                InputSource sheetSource = new InputSource(sheet);
                parser.parse(sheetSource);
                sheet.close();

                Debug.print(this.getClass().getSimpleName(), "Done processing sheet");
            }// end while-loop
        } catch (SAXException | OpenXML4JException | IOException e) {
            e.printStackTrace();
        } finally {
            isParsing = false;
            Debug.print(this.getClass().getSimpleName(), "----- FINISHED : " + ((System.currentTimeMillis() - runningTime) / 1000) + " seconds -----");
        }// end try-catch
    }// end parseToDB()


    //
    public XMLReader getSheetParser(SharedStringsTable sharedStringsTable) throws SAXException {
        XMLReader parser = XMLReaderFactory.createXMLReader();
        ContentHandler handler = new SheetHandler(sharedStringsTable);
        parser.setContentHandler(handler);

        return parser;
    }// end getSheetParser(SharedStringsTable sharedStringsTable)


    // SHEET HANDLER CLASS
    private static class SheetHandler extends DefaultHandler {
        private SharedStringsTable sharedStringsTable;
        private boolean fromSST, isCellValue, isNumber;
        private String contents;


        //
        private SheetHandler(SharedStringsTable sharedStringsTable) {
            this.sharedStringsTable = sharedStringsTable;
        }// end SheetHandler(SharedStringsTable sharedStringsTable)


        @Override
        public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
            // clear contents cache
            contents = "";

            // element row represents Row
            if(name.equals("row")) {
                String rowNumStr = attributes.getValue("r");
                Debug.print(this.getClass().getSimpleName(), "Row# " + rowNumStr);
            }
            // element c represents Cell
            else if(name.equals("c")) {
                // attribute r represents the cell reference
                Debug.print(this.getClass().getSimpleName(), attributes.getValue("r") + " - ");

                // attribute t represents the cell type
                String cellType = attributes.getValue("t");
                if (cellType != null && cellType.equals("s")) {
                    // cell type s means value will be extracted from SharedStringsTable
                    fromSST = true;
                } else if(cellType == null) {
                    // *likely a number
                    isNumber = true;
                }
            }
            // element v represents value of Cell
            else if(name.equals("v")) {
                isCellValue = true;
            }
        }// end startElement(String uri, String localName, String name, Attributes attributes)


        @Override
        public void characters(char[] ch, int start, int length) {
            if(isCellValue)
                contents += new String(ch, start, length);
        }// end characters(char[] ch, int start, int length)


        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
            if(isCellValue) {
                if(fromSST) {
                    int index = Integer.parseInt(contents);
                    contents = new XSSFRichTextString(sharedStringsTable.getEntryAt(index)).toString();

                    Debug.print(this.getClass().getSimpleName(), "Contents: " + contents + " >>");

                    isCellValue = false;
                    fromSST = false;
                } else if(isNumber) {
                    Debug.print(this.getClass().getSimpleName(), "Contents (num?): " + contents + " >>");
                }
            }
        }// end endElement(String uri, String localName, String name)
    }// end class SheetHandler
}// end class ExcelParser
0
Drew Adams 7 Дек 2018 в 09:46

1 ответ

Лучший ответ

Благодаря предложению @Gagravarr я смог найти решение. Я нашел обновленная реализация файла XLSX2CSV.java (при поиске эффективного способа решения моей проблемы), который печатал каждую строку файла .xlsx в CSVWriter. Я скорректировал код в методе endRow (), чтобы вставить новую строку в мою базу данных вместо записи в CSVWriter. Это все еще немного медленно, но у меня больше нет проблем с памятью!

1
Drew Adams 9 Дек 2018 в 07:55