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方式基本解决)
- 对应大量数据的保存,速度一直很慢,尽管我这里采用了批量提交的方式(目前这问题我依然没找到很好的方案,若是有同行看到的,还但愿多多指教)