写点什么

🏆【Alibaba 中间件技术系列】「EasyExcel 实战案例」实战研究一下 EasyExcel 如何从指定文件位置进行读取数据

作者:浩宇天尚
  • 2021 年 12 月 19 日
  • 本文字数:3859 字

    阅读完需:约 13 分钟

🏆【Alibaba中间件技术系列】「EasyExcel实战案例」实战研究一下EasyExcel如何从指定文件位置进行读取数据

EasyExcel 的使用背景

工作中总会遇到对 Excel 读写功能,之前接触过 EasyExcel,后续我们基本上用它代替了传统的 POI 和 JXL、甚至还有一个 EasyPOI 技术。

EasyExcel 的时候痛点

使用的 EasyExcel 时候,一般场景下表头比较传统,也不复杂,但是这次呢表头稍微有点复杂,读取数据要从指定的位置开始,要从指定位置开始读取 EasyExcel,所以呢在不断的摸索之后,找到了合适的解决方法。

EasyExcel 对比其他框架

平常用 poi 读取 excel 数据量少,加上 EasyExcel 读取 Excel 有点复杂,所以一直也没在项目中使用 EasyExcel,直到有一回要读取的数据量太大,使用 poi 读取 Excel 在创建 Workbook -> WorkbookFactory.create(inputStream) 时就异常了,分配很多内存也不好使,所以放弃使用 poi 转使用 EasyExcel。


Java 解析、生成 Excel 比较有名的框架有 Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi 有一套 SAX 模式的 API 可以一定程度的解决一些内存溢出的问题,但 POI 还是有一些缺陷,比如 07 版 Excel 解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel 重写了 poi 对 07 版 Excel 的解析,能够原本一个 3M 的 excel 用 POI sax 依然需要 100M 左右内存降低到几 M,并且再大的 excel 不会出现内存溢出,03 版依赖 POI 的 sax 模式。


在上层做了模型转换的封装,让使用者更加简单方便 --EasyExcel 使用 EasyExcel 读取 Excel 时一直在想如何简化读取方式,不用读取每个 Excel 都创建一个 XXDataListene 监听器类,刚开始想,把 DataListener 加上泛型,共用一个 DataListener<T>,但是还涉及到如何传递 Dao 和每个 Dao 如何保存数据,而且保存数据前可能还需要对数据进行不同的处理。

EasyExcel 的编程模式

EasyExcel 开源挺久了,但使用上感觉有点让人望而生怯,刚开始看官方文档上读取 Excel 挺简单的,只需要一行代码,继续细看的话还需要创建一个回调监听器,有点复杂呀(每个 Excel 都需要创建一个单独的回调监听器类)。

EasyExcel 读取的指定位置

要开始读取数据,第 8 行才是真正的数据,直接上代码,headRowNumber(),不写默认是 1,即就是从第二行开始读数据。


    /**     * 读取文件信息数据     * @param filePath     * @param headNum     */    public ContactInfoExcelDataListener read(String filePath , int headNum){        EasyExcel.read(filePath, this).head(ContactInfoExcelEntity.class).autoCloseStream(true                ).autoTrim(true).ignoreEmptyRow(true).sheet()                // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行                .headRowNumber(Math.max(headNum,NumberUtils.BYTE_ZERO)).doRead();        return this;    }
/** * 读取文件信息数据 * @param filePath */ public ContactInfoExcelDataListener read(String filePath){ EasyExcel.read(filePath, this).head(ContactInfoExcelEntity.class).autoCloseStream(true).autoTrim(true).ignoreEmptyRow(true).sheet() // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行 .doRead(); return this; }
/** * 读取文件信息数据 * @param inputStream * @param headNum */ public ContactInfoExcelDataListener read(InputStream inputStream, int headNum){ EasyExcel.read(inputStream, this).head(ContactInfoExcelEntity.class).autoCloseStream(true).autoTrim(true).ignoreEmptyRow(true).sheet() // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行 .headRowNumber(Math.max(headNum,NumberUtils.BYTE_ZERO)).doRead(); return this; }
复制代码

导入数据的流程

表头校验

invokeHeadMap()方法



/** * 调用头部 * @param map * @param analysisContext */ @Override public void invokeHead(Map<Integer, CellData> map, AnalysisContext analysisContext) { log.info("【start read the excel head data】:{}",map); // 判断标记头是否存在 //基本都会走到这里,全部放权交接给invoke方法,并且巧用作为我们锁初始化操作的控制赋值,切记如果headNum = 0 此方法很有可能不会触发,慎用! //一次性筷子!赋值为1,目前只是实现了相关的单节点同步锁,如果未来扩展了相关的分布式节点,需要采用分布式锁机制进行控制!锁范围需要进行控制 try { int titleRows = map.size(); // 头部的中断处理机制! failureDataCount = preValidate?orginalHead.size() != titleRows?NumberUtils.INTEGER_ONE: NumberUtils.BYTE_ZERO:NumberUtils.BYTE_ZERO; // 进行置位 if(preValidate && (failureDataCount.intValue() == NumberUtils.INTEGER_ONE)){ causeByHeadFormatAbort = Boolean.TRUE; } if(!isMockFlag) { // TODO 基本不会走到这里:一般我们如果需要可以使用此方法作为初始化资源使用的目的! //Preconditions.checkNotNull(clueLogic,"not support clueLogic is inject this class subject!"); if (Objects.isNull(clueLogic)) { clueLogic = SpringUtils.getBean(ClueLogic.class); } customerImportVO = new CustomerImportVO(); // 此部分主要是为了减少不必要的内存空间的申请 tempDataList = Lists.newArrayListWithExpectedSize(batchSizeUnit); }// syncLockController.lock(); } catch (Exception e) { log.error("invoke the analysis the title head info data is failure!",e); throw new UnsupportedOperationException("invoke the analysis the title head info data is failure!",e); } log.info("【finished read the excel head data】"); }
复制代码
数据处理

invoke()方法


一条一条数据解析 invoke()方法 ,方法里面是我业务逻辑,数据校验。invoke 就是每行具体的数据值


    /**     * 调用操作处理控制机制     * @param excelEntity     * @param context     */    @Override    public void invoke(ContactInfoExcelEntity excelEntity, AnalysisContext context) {        log.info("----【start read the excel main data:{}】----",excelEntity);        if(batchSizeUnit <= tempDataList.size()){            CustomerImportVO customerImportVO = clueLogic.startCallTaskProxy(contactInfoImportParam,tempDataList);            // 合并计算结果->更新为最新的结果            this.customerImportVO.merge(customerImportVO);            tempDataList.clear();            tempDataList = Lists.newArrayListWithExpectedSize(batchSizeUnit);        }else{            tempDataList.add(excelEntity);        }        log.info("【finished read the excel main data】");    }
复制代码
执行中断

hasNextdoAfterAllAnalysed()方法


/** * 是否拥有下一次执行 * [@param](https://my.oschina.net/u/2303379) context * [@return](https://my.oschina.net/u/556800) */[@Override](https://my.oschina.net/u/1162528)public boolean hasNext(AnalysisContext context) {    return causeByHeadFormatAbort?Boolean.FALSE:isSupportAbort? failureDataCount <= 0 :Boolean.TRUE;}
复制代码
数据完成

doAfterAllAnalysed()方法


所有数据解析完, doAfterAllAnalysed()方法,里面写的有保存数据方法。


    /**     * 执行结束的回调机制     * @param analysisContext     */    @Override    public void doAfterAllAnalysed(AnalysisContext analysisContext) {        log.info("【doAfterAllAnalysed the process】");        try {            CustomerImportVO customerImportVO = clueLogic.startCallTaskProxy(contactInfoImportParam,tempDataList);            this.customerImportVO.merge(customerImportVO);            finisheDataResult = Boolean.TRUE;        }catch (Exception e){            log.error("execute finially the flush data is failure!");            //TODO 收尾的数据信息如何做到一致性和完成补偿!            finisheDataResult =  Boolean.FALSE;        } finally {            tempDataList.clear();//            syncLockController.unlock();        }    }
复制代码

资料参考

https://blog.csdn.net/weixin_39929602/article/details/112189135?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link

发布于: 3 小时前阅读数: 6
用户头像

浩宇天尚

关注

🏆 InfoQ写作平台-签约作者 🏆 2020.03.25 加入

【个人简介】酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“ 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、APM专题及微服务/分布式体系等

评论

发布
暂无评论
🏆【Alibaba中间件技术系列】「EasyExcel实战案例」实战研究一下EasyExcel如何从指定文件位置进行读取数据