Apache 兴趣点:OutOfMemory
Apache POI: OutOfMemory
我正在为 Kindle Fire 开发一个应用程序,它从保管箱中提取一个 .xlsx 文件并使用 Apache POI 将数据解析到 SQLite 数据库中(单个 table 具有 10 个属性 - 我会打破一旦我可以让解析工作,它就会变成更多 tables)。该文件刚好超过 2MB(约 28,000 行,每行 10 列),所以当我最初开始在物理设备上进行测试时(模拟器运行良好,但速度非常慢),我 运行 遇到了 OutOfMemoryErrors。我做了很多挖掘,发现我可以实现 SAX 来减少我使用的内存量。但是,我不太确定如何将所有数据放入我的 table - 根据我看过的示例代码,每个单元格(至少据我所知)都是单独评估的,所以我不能在每一行迭代中进行一个查询。我 运行 遇到的另一个问题是我有一列数字(价格)打印到控制台(通过 Debug.print())两次,我不知道为什么。我对此束手无策 - 我花了几天时间解决 Dropbox 和 POI 的不同问题,但这个问题让我感到难过。到目前为止,我将这三个用作 templates/guides(主要是后者):
http://www.saxproject.org/quickstart.html
https://dzone.com/articles/introduction-to-apache-poi-library
我已经检查了这些(以及其他几个):
How to read XLSX file of size >40MB
http://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api
Reading an Excel sheet using POI's XSSF and SAX (Event API)
这是一个相当简单的应用程序,所以我不需要太花哨的东西 - 此时只需要 运行。所以我想我的问题是:SAX 是我避免内存问题的最佳方式吗?如果是这样,我如何实现它以将每一行准确地解析到我的数据库中?如果不是,我应该朝哪个方向移动?这是我的解析 class(运行ningTime 和 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
感谢@Gagravarr 的建议,我得以制定出解决方案。我发现 XLSX2CSV.java 文件的 updated implementation(在寻找解决我的问题的有效方法时)将 .xlsx 文件的每一行打印到 CSVWriter 中。我调整了 endRow() 方法中的代码,将新行插入到我的数据库中,而不是写入 CSVWriter。它仍然有点慢,但我不再有内存问题了!
我正在为 Kindle Fire 开发一个应用程序,它从保管箱中提取一个 .xlsx 文件并使用 Apache POI 将数据解析到 SQLite 数据库中(单个 table 具有 10 个属性 - 我会打破一旦我可以让解析工作,它就会变成更多 tables)。该文件刚好超过 2MB(约 28,000 行,每行 10 列),所以当我最初开始在物理设备上进行测试时(模拟器运行良好,但速度非常慢),我 运行 遇到了 OutOfMemoryErrors。我做了很多挖掘,发现我可以实现 SAX 来减少我使用的内存量。但是,我不太确定如何将所有数据放入我的 table - 根据我看过的示例代码,每个单元格(至少据我所知)都是单独评估的,所以我不能在每一行迭代中进行一个查询。我 运行 遇到的另一个问题是我有一列数字(价格)打印到控制台(通过 Debug.print())两次,我不知道为什么。我对此束手无策 - 我花了几天时间解决 Dropbox 和 POI 的不同问题,但这个问题让我感到难过。到目前为止,我将这三个用作 templates/guides(主要是后者):
http://www.saxproject.org/quickstart.html
https://dzone.com/articles/introduction-to-apache-poi-library
我已经检查了这些(以及其他几个):
How to read XLSX file of size >40MB
http://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api
Reading an Excel sheet using POI's XSSF and SAX (Event API)
这是一个相当简单的应用程序,所以我不需要太花哨的东西 - 此时只需要 运行。所以我想我的问题是:SAX 是我避免内存问题的最佳方式吗?如果是这样,我如何实现它以将每一行准确地解析到我的数据库中?如果不是,我应该朝哪个方向移动?这是我的解析 class(运行ningTime 和 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
感谢@Gagravarr 的建议,我得以制定出解决方案。我发现 XLSX2CSV.java 文件的 updated implementation(在寻找解决我的问题的有效方法时)将 .xlsx 文件的每一行打印到 CSVWriter 中。我调整了 endRow() 方法中的代码,将新行插入到我的数据库中,而不是写入 CSVWriter。它仍然有点慢,但我不再有内存问题了!