【Alibaba工具型技术系列】「EasyExcel技术专题」实战研究一下 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,即就是从第二行开始读数据。

java 复制代码
    /**
     * 读取文件信息数据
     * @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;
    }
导入数据的流程

基本都会走到这里,全部放权交接给invoke方法,并且巧用作为我们锁初始化操作的控制赋值,切记如果headNum = 0 此方法很有可能不会触发,慎用!

表头校验

目前只是实现了相关的单节点同步锁,如果未来扩展了相关的分布式节点,需要采用分布式锁机制进行控制!锁范围需要进行控制

invokeHeadMap()方法
java 复制代码
    /**
     * 调用头部
     * @param map
     * @param analysisContext
     */
    @Override
    public void invokeHead(Map<Integer, CellData> map, AnalysisContext analysisContext) {
        log.info("【start read the excel head data】:{}",map);// 判断标记头是否存在

        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 就是每行具体的数据值

java 复制代码
    /**
     * 调用操作处理控制机制
     * @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()方法
java 复制代码
    /**
     * 是否拥有下一次执行
     * [@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()方法,里面写的有保存数据方法。

java 复制代码
    /**
     * 执行结束的回调机制
     * @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();
        }
    }

总结一下

  • 快速读写:EasyExcel 支持 Excel 2003 和 Excel 2007 格式,并提供高效的读写性能。它使用了 NIO(新输入/输出)技术,使得读写操作更加快速。
  • 简单易用:EasyExcel 的 API 设计简洁明了,易于使用。开发者只需编写少量代码,即可完成 Excel 文件的读写操作。它还支持链式编程,使代码更加简洁。
  • 支持自定义:EasyExcel 提供了丰富的自定义选项,允许开发者根据需要调整 Excel 文件的格式、样式等。它还支持自定义公式、条件格式等功能,满足各种业务需求。
  • 灵活的配置:EasyExcel 支持多种配置方式,如属性配置、注解配置等。开发者可以根据项目需求选择合适的配置方式,使得 Excel 文件的处理更加灵活。
相关推荐
专注API从业者43 分钟前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠1 小时前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY1 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克32 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠3 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌3 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局3 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源3 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19434 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解