使用sax解析大量的Excel数据
为什么要用sax解析excel文件呢?
先把其他几个简单的提一下,2003版的excel(.xls)文件咱们可以用HSSFWorkbook来读写。2007版的excel(.xlsx)文件咱们可以用XSSFWorkbook来读写。当然低内存高效写也可以使用SXSSF,SXSSF是内存中保留一定行数数据,超过行数,将索引最低的数据刷入硬盘。但是读文件时数据量级一提升,传统方式cpu消耗大而且容易内存溢出(OutOfMemoryError)。
这时候就需要咱们今天介绍的主角登场,基于事件驱动,SAX的方式解析excel,cup和内存消耗低(只读)。网上介绍原理和简单应用的博客有很多,随便看看其他博客就可以了解事件模式读取excel。我要说的是我在工作中运用遇到的问题以及对其他大佬博客当中一些介绍的更正。说到更正真的是火大,好几个博客都是大佬级别的排名,但是有些地方真不想说了,大家一定要养成看人家官方文档的习惯。
官方文档http://poi.apache.org/components/spreadsheet/how-to.html#XSSF+and+SAX+%28Event+API%29
可以看这个官方文档,也可以看其他人的博客,这一点有很多人讲(我今天要说的是比这个更完善的一版),实际上sax解析的核心就是自己写一个处理器。继承DefaultHandler 就行(DefaultHandler 实现的是ContentsHandler接口,等会儿提的完整版用到的处理器实现的是SheetContentsHandler接口),咱们写的处理器里面会依次调用startElement(),characters(),endElement。characters()方法要注意这个方法里面咱们new String(ch, start, length)得到的并不是单元格的值,得到的是得到单元格对应的索引值或是内容值,如果单元格类型是字符串、INLINESTR、数字、日期得到的是索引值,如果单元格类型是布尔值、错误、公式,则是内容值。得到索引值了再从SharedStringsTable(这个里面装的是所有sheet的字符串,共享的)里面根据索引取。SharedStringsTable这个在new咱们处理器的时候要传过去。但是要注意了这里面装了所有非数字的单元格。(重点说三遍非数字非数字非数字,啥子意思哇,就是咱们的处理器里面传了这个共享字符表,然后从里面取单元格值,但是数字给丢了,数字不在这个容器里面,言外之意咱们这一版只能处理非数字的excel文件,别抬杠你自己数字转字符串的不提,人家文档底下说的很清楚了,看我画出来的部分)
数字也要能导入参考这篇官方文档https://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java
当然小生不才,自己在工作中根据官方的代码稍作修改了点,为了贴合我的业务场景。以下就是我自己封装的工具类
package com.cqmc.cqiop.portal.autobusinessMg.util;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
import org.apache.poi.xssf.model.StylesTable;
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.XMLReaderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/**
* @program: cqiop-core
* @description: sax解析
* @author:
* @create: 2019-12-19 12:31
**/
public class XLSXTOLIST {
private final OPCPackage xlsxPackage;
/**
* 从最左边开始读取的列数
*/
private final int minColumns;
/**
* 数据目的地
*/
private final PrintStream output;
//结果List
private static List<List<String>> dataList=new ArrayList<>();
//每行List
private static List<String> rowList=new ArrayList();
private class SheetToCSV implements SheetContentsHandler {
private boolean firstCellOfRow;
private int currentRow = -1;
private int currentCol = -1;
private void outputMissingRows(int number) {
for (int i=0; i<number; i++) {
for (int j=0; j<minColumns; j++) {
// output.append(',');
}
// output.append('\n');
}
}
@Override
public void startRow(int rowNum) {
// 如果有间隙,则输出缺少的行(看自己需求,空行需要输出还是屏蔽)
//outputMissingRows(rowNum-currentRow-1);
firstCellOfRow = true;
currentRow = rowNum;
currentCol = -1;
}
@Override
public void endRow() {
// 确保最小列数
for (int i=currentCol; i<minColumns; i++) {
// output.append(',');
}
// output.append('\n');
dataList.add(new ArrayList(rowList));
rowList.clear();
}
@Override
public void cell(String cellReference, String formattedValue) {
if (firstCellOfRow) {
firstCellOfRow = false;
} else {
// output.append(',');
}
// 处理缺失的单元格
if(cellReference == null) {
// cellReference = new CellAddress(currentRow, currentCol).formatAsString();
}
int thisCol = (new CellReference(cellReference)).getCol();
int missedCols = thisCol - currentCol - 1;
//第currentRow+1行 第thisCol列
for (int i=0; i<missedCols; i++) {
// output.append(',');
rowList.add("");
}
currentCol = thisCol;
// 数字和字符串的处理
try {
Double.parseDouble(formattedValue);
rowList.add(formattedValue);
// output.append(formattedValue);
} catch (NumberFormatException e) {
// output.append('"');
// output.append(formattedValue);
rowList.add(formattedValue);
// output.append('"');
}
}
@Override
public void headerFooter(String text, boolean isHeader, String tagName) {
}
}
public XLSXTOLIST(OPCPackage pkg, PrintStream output, int minColumns) {
this.xlsxPackage = pkg;
this.output = output;
this.minColumns = minColumns;
}
public void processSheet(
StylesTable styles,
ReadOnlySharedStringsTable strings,
SheetContentsHandler sheetHandler,
InputStream sheetInputStream) throws IOException, SAXException {
DataFormatter formatter = new DataFormatter();
InputSource sheetSource = new InputSource(sheetInputStream);
//XMLReader sheetParser = XMLHelper.newXMLReader();
XMLReader sheetParser = XMLReaderFactory.createXMLReader();
ContentHandler handler = new XSSFSheetXMLHandler(
styles, strings, sheetHandler, formatter, false);
sheetParser.setContentHandler(handler);
sheetParser.parse(sheetSource);
}
public void process() throws IOException, OpenXML4JException, SAXException {
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);
XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
StylesTable styles = xssfReader.getStylesTable();
XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
while (iter.hasNext()) {
try (InputStream stream = iter.next()) {
processSheet(styles, strings, new SheetToCSV(), stream);
}
}
}
public static List<List<String>> excelDataToArrayList(String path){
long start = System.currentTimeMillis();
try (OPCPackage p = OPCPackage.open(path, PackageAccess.READ)) {
XLSXTOLIST XLSXTOLIST = new XLSXTOLIST(p,null, -1);
XLSXTOLIST.process();
long end = System.currentTimeMillis();
System.out.println("解析excel耗时:"+(end-start)/1000+"秒,折算毫秒耗时:"+(end-start)+"毫秒");
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (OpenXML4JException e) {
e.printStackTrace();
}
return dataList;
}
public static List<List<String>> excelDataToArrayList(InputStream in){
long start = System.currentTimeMillis();
try (OPCPackage p = OPCPackage.open(in)) {
XLSXTOLIST XLSXTOLIST = new XLSXTOLIST(p, null, -1);
XLSXTOLIST.process();
long end = System.currentTimeMillis();
System.out.println("解析excel耗时:"+(end-start)/1000+"秒,折算毫秒耗时:"+(end-start)+"毫秒");
}catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (OpenXML4JException e) {
e.printStackTrace();
}
return dataList;
}
public static void main(String[] args) {
System.out.println(excelDataToArrayList("D:/可选包.xlsx").size());
}
}
这是我自己修改的一个工具类,亲测可用,六列 三十万多万的数据七 八秒能搞定,sax解析的好处不仅是一个高效更主要是低内存。欠妥之处,欢迎交流。
上一篇: Core Dump核心转储
下一篇: android SAX解析XML