前面我们介绍了用户模式如何解析excel,但是用户模式局限很大,其并不能解析大数据量的excel。我做过测试,在16G内存上的机子,07版excel导入6W条数据,内存一下彪了2个G,由于之前忘截图了,所以此处没有图,下面,我将介绍在大批量数据时候,如何解析excel。
当然是用 事件驱动模式(其实就是一种流式读取的名字)了!
网上有很多相似的代码,我也参考了很多,但是我发现不论哪个都有很大的问题,比如对公式和数字日期的解析。
下面不多说,直接上代码。参考了网上很多的代码,也给自己记录一下。
首先07版的,要继承 DefaultHandler 把他当做xml解析。这个网上有很多说明,我就不赘述了。xlsx改成rar后解压得到如下图。
package com.youth.excel_youth.util;
import com.youth.excel_youth.constants.Constants;
import com.youth.excel_youth.constants.XSSFDataType;
import com.youth.excel_youth.service.IRowReader;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
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;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author: youth_1231
* @Date: 2019/1/24 0024 15:39
* @Description: 抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析
* xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低
* 内存的耗费,特别使用于大数据量的文件。
*/
public class Excel07Parser extends DefaultHandler {
//共享字符串表
private SharedStringsTable sst;
//上一次的内容
private String readValue;
/**
* 存放一行中的数据
*/
private String[] rowList;
private int colIdx;
private int sheetIndex = -1;
//当前行
private int curRow = 0;
/**
* T元素标识
*/
private boolean isTElement;
/**
* 单元格类型
*/
private XSSFDataType dataType;
private StylesTable stylesTable;
private short dataFormat;
private String dataFormatString;
private IRowReader rowReader;
/**
* 可以将有的sheet页相关参数放到一个对象中,比如sheet中的错误,sheetName,业务数据等等,
* 对象放到集合中,其中的数据建议超过2000条持久化一次
*/
// private List<XXX> xxxxx;
public void setRowReader(IRowReader rowReader){
this.rowReader = rowReader;
}
/**
* 遍历工作簿中所有的电子表格
* @param filename
* @throws Exception
*/
public void process(String filename) throws Exception {
OPCPackage pkg = OPCPackage.open(filename);
XSSFReader r = new XSSFReader(pkg);
this.stylesTable = r.getStylesTable();
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) r.getSheetsData();
while (sheets.hasNext()) {
curRow = 0;
sheetIndex++;
InputStream sheet = sheets.next();
/*获取当前sheet名称,有些同学需要*/
String sheetName = sheets.getSheetName();
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
}
public XMLReader fetchSheetParser(SharedStringsTable sst)
throws SAXException {
XMLReader parser = XMLReaderFactory
.createXMLReader();
this.sst = sst;
parser.setContentHandler(this);
return parser;
}
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
// c => 单元格
if ("c".equals(name)) {
colIdx = getColumn(attributes);
dataFormat = -1;
dataFormatString = null;
// 如果下一个元素是 SST 的索引,则将nextIsString标记为true
String cellType = attributes.getValue("t");
String cellStyle = attributes.getValue("s");
this.dataType = XSSFDataType.NUMBER;
if ("b".equals(cellType)) {
this.dataType = XSSFDataType.BOOLEAN;
} else if ("e".equals(cellStyle)) {
this.dataType = XSSFDataType.ERROR;
} else if ("s".equals(cellStyle)) {
this.dataType = XSSFDataType.SSTINDEX;
} else if ("inlineStr".equals(cellStyle)) {
this.dataType = XSSFDataType.INLINESTR;
} else if ("str".equals(cellStyle)) {
this.dataType = XSSFDataType.FORMULA;
}
if (cellStyle != null) {
int styleIndex = Integer.parseInt(cellStyle);
XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
dataFormat = style.getDataFormat();
dataFormatString = style.getDataFormatString();
/**
* 07版本当前只发现了57 58 的时候formatString为空
*/
if (!Constants.EXCEL_FORMAT_INDEX_DATA_EXACT_NY.equals(dataFormat) && !Constants.EXCEL_FORMAT_INDEX_DATA_EXACT_YR.equals(dataFormat)
&& !Constants.EXCEL_FORMAT_INDEX_TIME_EXACT.contains(dataFormat)
&& dataFormatString == null){
this.dataType = XSSFDataType.NULL;
dataFormatString = BuiltinFormats.getBuiltinFormat(dataFormat);
}
}
}
//当元素为t时
if("t".equals(name)){
isTElement = true;
} else {
isTElement = false;
}
// 解析到一行开始处,初始化数据
if("row".equals(name)){
rowList = new String[getColsNum(attributes)];
}
// 置空
readValue = "";
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
if (isTElement) {
rowList[colIdx] = readValue.trim();
isTElement = false;
} else if ("v".equals(name)){
getValue();
rowList[colIdx] = readValue;
} else {
//如果标签名为 row,这说明已经到行尾,调用getRows()方法
if ("row".equals(name)){
rowReader.getRows(sheetIndex,curRow,new ArrayList<>(Arrays.asList(rowList)));
curRow++;
}
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
//得到单元格内容的值
readValue += new String(ch, start, length);
}
/**
* 事件模式: 得到当前cell在当前row的位置
* @param attributes
* @return
*/
private int getColumn(Attributes attributes){
String name = attributes.getValue("r");
int column = -1;
for (int i = 0; i < name.length(); ++i) {
if (Character.isDigit(name.charAt(i))){
break;
}
int c = name.charAt(i);
column = (column + 1) * 26 + c - 'A';
}
return column;
}
/**
* 事件模式: 得到当前cell在当前row的位置
* @param attributes
* @return
*/
private int getColsNum(Attributes attributes){
String s