🏆【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,但是还涉及到如何传递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(); } }
资料参考
- Java, 如何从Excel中读取数据到文件中
- python 在excel文件中写入date日期数据,以及读取excel日期数据,如何在python中正确显示date日期。
- MATLAB如何读取excel文件中的数据?
- 应用集成实战系列:如何进行文件交换
- 对应诺言,写了篇文章,“如何使用Serialization 进行文件存储/读取数据 (上)”, 请大家评评。
- matlab中如何读取TXT数据文件中指定行的数据?
- 如何用java读取csv文件指定行列的数据,并将csv中数据元素随机置零后保存到另一个csv文件中
- 关于Excel操作编写的一个软件设计构思案例[连载] --如何把处理好后的数据导出Excel文件中(含背景\字体颜色设置)
- 如何从文件中提取指定位置的数据????
- 用C#编程从数据库中读取图片数据导进Excel文件的方法(如何从数据库中读取保存的文件,直接打开,中间不保存到本地)
- poi读取Excel文件并进行数据类型转换实例
- Python读取文件,路径问题,以及在对数据进行处理之前如何查看数据的相关信息
- Python使用pandas读取Excel文件数据和预处理小案例
- Java导入Excel文件返回数据对应属性类字段,Java导出Excel文件如何让数据对应指定的标题字段
- fseek fread 指定文件指针位置开始读取数据
- 关于Excel操作编写的一个软件设计构思案例[连载] --如何打开Excel文件,获取需要列的数据显示到表格内做修改
- 如何处理Oledb中EXCEL驱动读取EXCEL文件中字段长度大于255字符时出现的"数据截断"问题.
- net控件中数据导到Excel的格式 首先,我们了解一下excel从web页面上导出的原理。当我们把这些数据发送到客户端时,我们想让客户端程序(浏览器)以excel的格式读取它,所以把mime类型设为:application/vnd.ms-excel,当excel读取文件时会以每个cell的格式呈现数据,如果cell没有规定的格式,则excel会以默认的格式去呈现该cell的数据。这样就给我们提供了自定义数据格式的空间,当然我们必须使用excel支持的格式。下面就列出常用的一些格式: 1) 文本
- 如何处理ODBC中EXCEL驱动读取EXCEL文件中字段长度大于255字符时出现的"数据截断"问题.
- 用C#读取Excel文件:从指定单元格开始向右向下读取数据