场景:
需要读取超过5万行的数据,然后经过业务处理后,再写入到excel中。
问题描述:
当数据量大的时候,在读和写如果采用普通的poi给予的方法都会报heap OutOfMemoryError的问题。
解决方案:
1、读数据解决方案
import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map;import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.openxml4j.opc.OPCPackage;
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;/**
-
XSSF and SAX (Event API)
*/
public abstract class XxlsAbstract extends DefaultHandler {
private SharedStringsTable sst;
private String lastContents;
private boolean nextIsString;private int sheetIndex = -1;
private List<String> rowlist = new ArrayList<String>();
private int curRow = 0; //当前行
private int curCol = 0; //当前列索引
private int preCol = 0; //上一列列索引
private int titleRow = 0; //标题行,一般情况下为 0
private int rowsize = 0; //列数
private String flag ="";//保存报表类型
private List<Map<String,String>>listReportMap = null;
//excel 记录行操作方法,以行索引和行元素列表为参数,对一行元素进行操作,元素为 String 类型
// public abstract void optRows(int curRow, List<String> rowlist) throws SQLException ;//excel 记录行操作方法,以 sheet 索引,行索引和行元素列表为参数,对 sheet 的一行元素进行操作,元素为 String 类型
public abstract void optRows(int sheetIndex,int curRow, List<String> rowlist,String flag,List<Map<String,String>>listReportMap) throws SQLException;//只遍历一个 sheet,其中 sheetId 为要遍历的 sheet 索引,从 1 开始,1-3
public void processOneSheet(String filename,int sheetId,String flag,List<Map<String,String>> listReportMap) throws Exception {
this.listReportMap = listReportMap;
this.flag = flag;
OPCPackage pkg = OPCPackage.open(filename);
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();XMLReader parser = fetchSheetParser(sst); // rId2 found by processing the Workbook // 根据 rId# 或 rSheet# 查找sheet InputStream sheet2 = r.getSheet("rId"+sheetId); sheetIndex++; InputSource sheetSource = new InputSource(sheet2); parser.parse(sheetSource); sheet2.close();
}
/**
-
遍历 excel 文件
*/
public void process(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()) {
curRow = 0;
sheetIndex++;
InputStream sheet = sheets.next();
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
}
public XMLReader fetchSheetParser(SharedStringsTable sst)
throws SAXException {
XMLReader parser = XMLReaderFactory
.createXMLReader("org.apache.xerces.parsers.SAXParser");
this.sst = sst;
parser.setContentHandler(this);
return parser;
}public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
// c => 单元格
if (name.equals("c")) {
// 如果下一个元素是 SST 的索引,则将 nextIsString 标记为 true
String cellType = attributes.getValue("t");
String rowStr = attributes.getValue("r");
curCol = this.getRowIndex(rowStr);
if (cellType != null && cellType.equals("s")) {
nextIsString = true;
} else {
nextIsString = false;
}
}
// 置空
lastContents = "";
}public void endElement(String uri, String localName, String name)
throws SAXException {
// 根据 SST 的索引值的到单元格的真正要存储的字符串
// 这时 characters()方法可能会被调用多次
if (nextIsString) {
try {
int idx = Integer.parseInt(lastContents);
lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
.toString();
} catch (Exception e) {} } // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引 // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符 if (name.equals("v")) { String value = lastContents.trim(); value = value.equals("")?" ":value; int cols = curCol-preCol; if (cols>1){ for (int i = 0;i < cols-1;i++){ rowlist.add(preCol,""); } } preCol = curCol; rowlist.add(curCol-1, value); }else { //如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法 if (name.equals("row")) { int tmpCols = rowlist.size(); if(curRow>this.titleRow && tmpCols<this.rowsize){ for (int i = 0;i < this.rowsize-tmpCols;i++){ rowlist.add(rowlist.size(), ""); } } try { optRows(sheetIndex,curRow,rowlist,this.flag,this.listReportMap); } catch (SQLException e) { e.printStackTrace(); } if(curRow==this.titleRow){ this.rowsize = rowlist.size(); } rowlist.clear(); curRow++; curCol = 0; preCol = 0; } }
}
public void characters(char[] ch, int start, int length)
throws SAXException {
//得到单元格内容的值
lastContents += new String(ch, start, length);
}//得到列索引,每一列 c 元素的 r 属性构成为字母加数字的形式,字母组合为列索引,数字组合为行索引,
//如 AB45,表示为第(A-A+1)*26+(B-A+1)*26 列,45 行
public int getRowIndex(String rowStr){
rowStr = rowStr.replaceAll("[^A-Z]", "");
byte[] rowAbc = rowStr.getBytes();
int len = rowAbc.length;
float num = 0;
for (int i=0;i<len;i++){
num += (rowAbc[i]-'A'+1)*Math.pow(26,len-i-1 );
}
return (int) num;
}public int getTitleRow() {
return titleRow;
}public void setTitleRow(int titleRow) {
this.titleRow = titleRow;
}
} -
使用方法
这个方法是在读2007版本的时候,可以自己写一个类然后继承这个抽象类。重写optRows。
在146行可以看到,这个抽象类,会调用子类你重写的方法,所以再子类中的方法可以直接拿到该数据。这种读的方式速度很快,5w行数据也就6,7秒就可以搞定
2、写大量数据解决方案
可以将写出的数据批量写出,1w行数据一个sheet,或者分批写不同的excel。就我测试的数据来看。分批写不同的excel速度会比较快。
注:以上的读取代码来自于网络
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于