1. Excel解析工具easyexcel全面探索 1.1. 简介
之前我们想到Excel解析一般是使用POI,但POI存在一个严重的问题,就是非常消耗内存。所以阿里人员对它进行了重写从而诞生了easyexcel,它解决了过于消耗内存问题,也对它进行了封装让使用者使用更加便利
接下来我先一一介绍它所有的功能细节、如何使用及部分源码解析
1.2. Excel读 1.2.1. 例子 /** * 最简单的读 * <p>1. 创建excel对应的实体对象 参照{@link DemoData} * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} * <p>3. 直接读即可 */ @Test public void simpleRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); }官方说明也比较明确,使用简单fileName是路径+文件名,DemoData是Excel数据对应的实体类,DemoDataListener这看名字就是监听器,用来监听处理读取到的每一条数据
1.2.2. 源码解析 1.2.2.1. 核心源码XlsxSaxAnalyser它核心的Excel解析我认为是这个类XlsxSaxAnalyser,在它的构造方法中做了很多事
public XlsxSaxAnalyser(AnalysisContext analysisContext, InputStream decryptedStream) throws Exception { ... //从这开始将数据读取成inputStream流,缓存到了sheetMap XSSFReader xssfReader = new XSSFReader(pkg); analysisUse1904WindowDate(xssfReader, readWorkbookHolder); stylesTable = xssfReader.getStylesTable(); sheetList = new ArrayList<ReadSheet>(); sheetMap = new HashMap<Integer, InputStream>(); XSSFReader.SheetIterator ite = (XSSFReader.SheetIterator)xssfReader.getSheetsData(); int index = 0; if (!ite.hasNext()) { throw new ExcelAnalysisException("Can not find any sheet!"); } while (ite.hasNext()) { InputStream inputStream = ite.next(); sheetList.add(new ReadSheet(index, ite.getSheetName())); sheetMap.put(index, inputStream); index++; } } 1.2.2.2. doRead例子中真正开始做解析任务的是doRead方法,不断进入此方法,会看到真正执行的最后方法就是XlsxSaxAnalyser类的execute方法;可以看到如下方法中parseXmlSource解析的就是sheetMap缓存的真正数据
@Override public void execute(List<ReadSheet> readSheetList, Boolean readAll) { for (ReadSheet readSheet : sheetList) { readSheet = SheetUtils.match(readSheet, readSheetList, readAll, analysisContext.readWorkbookHolder().getGlobalConfiguration()); if (readSheet != null) { analysisContext.currentSheet(readSheet); parseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(analysisContext, stylesTable)); // The last sheet is read analysisContext.readSheetHolder().notifyAfterAllAnalysed(analysisContext); } } } 1.2.2.3. 概述DemoDataListener实现对应我们用户需要手写的代码,我们的监听器DemoDataListener中有两个实现方法如下,invoke就对应了上述代码中的parseXmlSource而doAfterAllAnalysed对应了上述方法中的notifyAfterAllAnalysed,分别表示了先解析每一条数据和当最后一页读取完毕通知所有监听器
@Override public void invoke(DemoData data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); if (list.size() >= BATCH_COUNT) { saveData(); list.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); LOGGER.info("所有数据解析完成!"); } 1.2.2.4. parseXmlSource具体实现看标识重点的地方,这是最核心的解析地
private void parseXmlSource(InputStream inputStream, ContentHandler handler) { InputSource inputSource = new InputSource(inputStream); try { SAXParserFactory saxFactory = SAXParserFactory.newInstance(); saxFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser saxParser = saxFactory.newSAXParser(); XMLReader xmlReader = saxParser.getXMLReader(); xmlReader.setContentHandler(handler); //重点 xmlReader.parse(inputSource); inputStream.close(); } catch (ExcelAnalysisException e) { throw e; } catch (Exception e) { throw new ExcelAnalysisException(e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { throw new ExcelAnalysisException("Can not close 'inputStream'!"); } } } }由于这层层深入非常多,我用一张截图来表现它的调用形式
1.2.2.5. notifyAfterAllAnalysed具体实现