Excel解析工具easyexcel全面探索

本文深入探讨了Excel解析工具EasyExcel的详细功能,包括读取、写入、文件上传与下载,以及各种实用技巧。通过源码解析,介绍了核心的读写操作,如`doRead`和`doWrite`方法,并展示了如何实现自定义字段转换、读取多页、指定表头行数、读取表头数据和异常处理。此外,还讨论了复杂头写入、图片导出、自定义样式、单元格合并等高级功能。
摘要由CSDN通过智能技术生成

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就对应了上述代码中的parseXmlSourcedoAfterAllAnalysed对应了上述方法中的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具体实现
  • 具体看notifyAfterAllAnalysed的代码,我们实现的DemoDataListener监听器继承AnalysisEventListener,而AnalysisEventListener实现ReadListener接口
    @Override
    public void notifyAfterAllAnalysed(AnalysisContext analysisContext) {
        for (ReadListener readListener : readListenerList) {
            readListener.doAfterAllAnalysed(analysisContext);
        }
    }

1.3. Excel写

1.3.1. 例子

  • 如下例子,使用还是简单的,和读比较类似
    /**
     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
     * <p>2. 直接写即可
     */
    @Test
    public void simpleWrite() {
        String fileName = TestFileUtil.getPath()   "write"   System.currentTimeMillis()   ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
    }
    
    private List<DemoData> data() {
        List<DemoData> list = new ArrayList<DemoData>();
        for (int i = 0; i < 10; i  ) {
            DemoData data = new DemoData();
            data.setString("字符串"   i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }

1.3.2. 源码解析

1.3.2.1. doWrite

  • 和读一样doWrite才是实际做事的,这次我们从这个入口跟进
    public void doWrite(List data) {
        if (excelWriter == null) {
            throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method");
        }
        excelWriter.write(data, build());
        excelWriter.finish();
    }

1.3.2.2. write

  • 很明显,write是核心,继续进入ExcelWriter类,看名字addContent就是添加数据了,由excelBuilderExcel建造者来添加,这是ExcelBuilderImpl
    public ExcelWriter write(List data, WriteSheet writeSheet, WriteTable writeTable) {
        excelBuilder.addContent(data, writeSheet, writeTable);
        return this;
    }

1.3.2.3. addContent

  • 可以看到如下,显示封装和实例化一些数据,创建了ExcelWriteAddExecutor写数据执行器,核心就是add方法了
    @Override
    public void addContent(List data, WriteSheet writeSheet, WriteTable writeTable) {
        try {
            if (data == null) {
                return;
            }
            context.currentSheet(writeSheet, WriteTypeEnum.ADD);
            context.currentTable(writeTable);
            if (excelWriteAddExecutor =
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值