POISAXReaderjava
poi處理excel分別提供比較友好的用戶模式以及比較底層的事件模式。其中,用戶模式提供良好的封裝,同時兼容2003以及2007以上的格式,使用至關方便。不過,代價是花費巨大的內存。只要超過6w條之後,基本是就是內存溢出了。
好在POI團隊也提供了更底層的的流處理模式eventMode,對於大數據的Xlsx文件的寫入,poi 3.8 提供SXSSF,採用緩存方式寫如文件。對於文件的讀取採用sax的方式直接讀取每一個sheet對應的xml文件。添加微信,回覆poi,邀請你加羣git
在POI中已經對SAX當時讀取對應的Sheet的xml文件已經作了基本的封裝,因此咱們僅僅須要實現接口SheetContentsHandler,就能夠完成SAX的方式讀取。這個接口中須要是實現三個方法github
這裏我主要參照poi XLSX2CSV.java
實現方式,須要提供對應的xlsx文件最大列數。其次,我在此基礎上作了擴展,在 endRow
提供了一個事件,當前處理的的行數據,讓這個解析功能更加獨立。
實現思路,在startRow
方法中構造一個List對象,在cell
函數中添加每一個單元內容,在endRow
函數中判斷當前列是否等於最大列數,若是不等循環補齊,並出發添加行事件
SheetSaxHandler詳細代碼web
protected class SheetSaxHandler implements SheetContentsHandler { private int currentRow = -1; private int currentCol = -1; private int minColumns; public void setMinColumns(int minColumns) { this.minColumns = minColumns; } public SheetSaxHandler(int minColumns) { super(); this.minColumns = minColumns; } public SheetSaxHandler() { } private List<SheetRowListener> listeners = new ArrayList<SheetRowListener>(); private List<String> lRows = new ArrayList<String>(); // 處理一行信息 public void rowAdded(SheetRowListener add) { listeners.add(add); } private void postRowAdded(List<String> row, int rowNum) throws SQLException { for (SheetRowListener hl : listeners) hl.addRow(row, rowNum); } @Override public void startRow(int rowNum) { currentRow = rowNum; currentCol = -1; lRows.clear(); } @Override public void endRow(int rowNum) { // 添加數據 for (int i = currentCol; i < minColumns; i++) { lRows.add(""); } try { postRowAdded(lRows, rowNum); } catch (SQLException e) { e.printStackTrace(); } } @Override public void cell(String cellReference, String formattedValue, XSSFComment comment) { if (cellReference == null) { cellReference = new CellAddress(currentRow, currentCol) .formatAsString(); } int thisCol = (new CellReference(cellReference)).getCol(); int missedCols = thisCol - currentCol - 1;//處理數據中間存在空白 for (int i = 0; i < missedCols; i++) { this.lRows.add(""); } currentCol = thisCol; // TODO 數據類型處理 try { Double.parseDouble(formattedValue); this.lRows.add(formattedValue); } catch (NumberFormatException e) { this.lRows.add(formattedValue); } } @Override public void headerFooter(String text, boolean isHeader, String tagName) { System.out.println(text + "==" + isHeader + "==" + tagName); } }
事件接口數據庫
interface SheetRowListener { void addRow(List<String> row, int rowNum); }
@Override public int saveToOracle(String filePath, String pcId) throws FileNotFoundException, EncryptedDocumentException, InvalidFormatException, IOException, ClassNotFoundException, SQLException, OpenXML4JException, SAXException, ParserConfigurationException { File f = new File(filePath); OPCPackage p = null; int num = 0; Connection conn = null; if (f.exists()) { try { JSONArray sheetCfgs = this.cfgJson.getJSONArray("sheets"); dataBuferRows = this.cfgJson.getInteger("dataBuferRows"); dataBuferRows = dataBuferRows == null ? 1000 : dataBuferRows; conn = ca.getConnection(ca.getSqlCfg(serverPath)); String importTime = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); p = OPCPackage.open(f, PackageAccess.READ); ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable( p); XSSFReader xssfReader = new XSSFReader(p); StylesTable styles = xssfReader.getStylesTable(); XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader .getSheetsData(); HashMap<Integer, JSONObject> hSheetCfg = new HashMap<Integer, JSONObject>(); for (int i = 0; i < sheetCfgs.size(); i++) { JSONObject sheetCfg = sheetCfgs.getJSONObject(i); hSheetCfg.put(sheetCfg.getInteger("sheetIndex"), sheetCfg); } int index = 1; while (iter.hasNext()) { InputStream sheetStream = iter.next(); if (hSheetCfg.containsKey(index)) { processSheet(styles, strings, new SheetSaxHandler(), sheetStream, hSheetCfg.get(index), conn, pcId, this.fileName, importTime); } index++; } p.close(); f = null; conn.close(); } catch (SQLException e) { conn.close(); conn = null; throw e; } } return num; }
public void processSheet(StylesTable styles, ReadOnlySharedStringsTable strings, SheetSaxHandler sheetHandler, InputStream sheetInputStream, final JSONObject sheetCfg, final Connection conn, String PcID, String fileName, String importTime) throws IOException, ParserConfigurationException, SAXException, SQLException { final PreparedStatement ps = conn.prepareStatement(ca.buildInsertSql( sheetCfg, PcID, fileName, importTime)); final int dataStartNum = sheetCfg.getIntValue("dataStartNum"); sheetHandler.setMinColumns(sheetCfg.getJSONArray("fieldReference") .size()); sheetHandler.rowAdded(new SheetRowListener() { @Override public void addRow(List<String> row, int rowNum) { if (rowNum < dataStartNum - 1) return; try { ca.setParamter(ps, sheetCfg, row, rowNum - dataStartNum); if (rowNum % dataBuferRows == 0) { ps.executeBatch(); ps.clearBatch(); } } catch (SQLException e) { try { ps.close(); conn.close(); throw e; } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } } }); XMLReader sheetParser = SAXHelper.newXMLReader(); DataFormatter formatter = new DataFormatter(); InputSource sheetSource = new InputSource(sheetInputStream); ContentHandler handler = new XSSFSheetXMLHandler(styles, null, strings, sheetHandler, formatter, false); sheetParser.setContentHandler(handler); sheetParser.parse(sheetSource); // 處理剩下的數據 ps.executeBatch(); ps.clearBatch(); // 關閉當前ps ps.close(); } `
在最初使用poi的用戶模式,很快的就完成一個excel文件的解析,很方便。隨着項目的逐漸深刻,處理的excel文件愈來愈大,用戶模式已經不能勝任。因而開始查找資料,在官網上看到了轉csv的實例。
這段代碼的主要功能將excel文件中的數據導入到oracle數據庫對應的表中,在實現功能方面,我主要遇到了如下問題緩存
- 解決excel文件解析內存泄露(2007之後文件採用sax方式基本解決)
- 對應大量數據的保存,速度一直很慢,儘管我這裏採用了批量提交的方式(目前這問題我依然沒找到很好的方案,若是有同行看到的,還但願多多指教)