java讀寫有大量數據的excel的方法

場景:

 須要讀取超過5萬行的數據,而後通過業務處理後,再寫入到excel中。java

問題描述:sql

當數據量大的時候,在讀和寫若是採用普通的poi給予的方法都會報heap OutOfMemoryError的問題。apache

解決方案:

一、讀數據解決方案
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 { <span style="color:#ff0000;"> optRows(sheetIndex,curRow,rowlist,this.flag,this.listReportMap);</span> } 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。xss

在146行能夠看到,這個抽象類,會調用子類你重寫的方法,因此再子類中的方法能夠直接拿到該數據。這種讀的方式速度很快,5w行數據也就6,7秒就能夠搞定測試

二、寫大量數據解決方案

能夠將寫出的數據批量寫出,1w行數據一個sheet,或者分批寫不一樣的excel。就我測試的數據來看。分批寫不一樣的excel速度會比較快。fetch

注:以上的讀取代碼來自於網絡this

相關文章
相關標籤/搜索