須要讀取超過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