深入解析 EasyExcel 组件原理与应用

深入解析 EasyExcel 组件原理与应用

官方:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网

在日常的 Java 开发工作中,处理 Excel 文件的导入导出是极为常见的需求。
今天,咱们就一起来深入了解一款非常实用的操作 Excel 的组件 ——EasyExcel,看看它究竟有着怎样的神奇之处,能让众多开发者青睐有加。

一、引言

想必大家都知道,在 Java 领域中,Apache POI 是处理 Excel 文件的老牌工具了。但随着业务发展,面对越来越大的 Excel 文件读写需求时,POI 暴露出了一些诸如内存溢出(OOM)等问题,使用起来稍显吃力。而 EasyExcel 的出现,很好地弥补了这些不足,它基于 POI 进行了优化封装,为我们提供了更加简洁高效的 Excel 处理方式。接下来,咱们就详细剖析一下 EasyExcel 的原理以及它的具体应用。

二、EasyExcel 与 Apache POI 的渊源

EasyExcel 其实是站在 Apache POI 这个 “巨人的肩膀” 上发展起来的。Apache POI 基于 OOXML(Office Open XML)标准(像 xlsx、docx、pptx 等以指定格式的 xml 为基础并以 zip 格式压缩的文件)和 OLE2 标准(包括 xls、doc、ppt 等二进制格式的文件),只要符合上述标准的文件它都能进行读写操作。

对于 xlsx 文件来说,它基于 OOXML 标准,能够解压成对应的 xml 文件,在解析后的 xml 文件里,我们重点关注 sharedStrings.xml 与 sheet1.xml 这两个 xml 节点,因为里面存放着我们想要处理的 Excel 内容。

不过,POI 在处理 Excel 时有传统的 POM 解析和 SAX 解析两种方式。传统的 POM 解析会把 Excel 中的所有 XML 节点解析成一棵 DOM 树,整棵 DOM 树加载进内存,这样做虽然方便对每个 XML 节点进行随机访问,但很容易导致内存溢出现象,像仅仅 3 列的 Excel 文件在 7 万行左右就可能出现内存溢出问题。

而 SAX 解析则不同,它采用边读取边处理的方式操作 XML,逐行扫描 XML 文档,遇到标签时触发解析处理器,进而触发相应的事件 Handler。这种方式内存占用小、效率高,但 POI 官方提供的 API 过于繁琐,使用起来步骤繁多,比如读取一个 Excel 文件,要先将 xlsx 格式的 Excel 文件解析成 OPCPackage 对象,再创建 XSSFReader 对象来解析获取对应 xlsx 下的 xml 内容,想要处理还得进一步创建内容处理器等等,着实有些麻烦。

三、EasyExcel 的原理剖析

(一)核心架构与流程

在 EasyExcel 中,有一个核心的类叫 ExcelAnalyserImpl,它就像是整个操作的调度中心。里面包含了 AnalysisContext 类(分析上下文,存放对应的文件 Inputstream 跟 AnalysisEventListener 事件监听处理器等)和 ExcelReadExecutor 类(默认是 XlsxSaxAnalyser 类,核心方法是 parseXmlSource (),负责逐行 SAX 解析 Excel)。

当 XlsxSaxAnalyser 解析类执行解析方法后,会调用 AnalysisEventProcessor 执行对应的事件方法,而在这个 processor 里真正执行操作的就是对应的事件监听器啦。官方默认提供了一个事件监听器,能把 Excel 解析成 BeanMap 的形式,然后根据我们传入的实体类,返回给我们想要的实体类对象。要是我们有定制化的功能需求,那就需要去了解这个监听器的核心方法了哦。

以下是一个简单的示例代码,展示如何使用 EasyExcel 读取 Excel 文件并将数据转换为自定义对象(假设我们有一个 User 实体类,包含 name 和 age 等属性):

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;// 自定义监听器,用于处理读取到的数据
class UserDataListener extends AnalysisEventListener<User> {private List<User> userList = new ArrayList<>();// 每解析一行数据就会调用此方法@Overridepublic void invoke(User user, AnalysisContext analysisContext) {userList.add(user);}// 全部解析完成后调用此方法@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {// 这里可以对读取到的所有数据进行后续操作,比如存入数据库等System.out.println("共读取到 " + userList.size() + " 条用户数据");}public List<User> getUserList() {return userList;}
}public class EasyExcelReadExample {public static void main(String[] args) throws IOException {String filePath = "your_excel_file_path.xlsx";  // 替换为实际的 Excel 文件路径// 读取 Excel 文件,第二个参数是自定义的监听器EasyExcel.read(new FileInputStream(filePath), User.class, new UserDataListener()).sheet().doRead();}
}
注解

  • 在上述代码中,首先定义了 UserDataListener 类,它继承自 AnalysisEventListener,并重写了 invoke 和 doAfterAllAnalysed 方法。invoke 方法用于在每解析一行 Excel 数据时将其封装成 User 对象并添加到 userList 中,而 doAfterAllAnalysed 方法则在整个 Excel 文件解析完成后执行,可以在这里进行一些后续的数据处理操作,比如将数据批量存入数据库等。
  • 在 EasyExcelReadExample 类的 main 方法中,通过 EasyExcel.read 方法来启动 Excel 文件的读取流程,传入文件输入流、要转换的目标对象类型(这里是 User 类)以及自定义的监听器,然后调用 sheet 和 doRead 方法完成读取操作。

(二)关键方法解析

1. doRead () 方法
doRead () 方法实际调用过程还挺有意思的,它先是调用了 ExcelReader 中的 read 方法与 finish 方法。read 方法实际调用了 Executor 中的 execute 方法,这个 execute 方法里会调用前面提到的核心方法 parseXmlSource 解析 excel,并且在一个 sheet 中所有数据读取完后,会调用事件调度器的 endSheet 方法,让每一个监听器执行对应的 doAfterAllAnalysed 方法。而 finish 方法则负责对读取过程持有容器中流的内容输出,之后清理存储的缓存,把开启的相应的流关闭。
2. doWrite () 方法
doWrite () 方法同样调用了 Writer 中的 write 方法和 finish 方法,这里的 finish 方法作用和读操作里类似哦。write 方法实际调用的是 Builder 中的 addContent 方法,在 addContent 方法里又会调用 Executor 中的 add 方法,针对传入的列表,逐个对象执行 addOneRowOfDataToExcel 方法进行逐行写入。在这个过程中,无论是创建行对象前后,还是填充完数据之后,我们都可以自定义处理器对流程进行相应的处理呢,非常灵活。

以下是一个使用 EasyExcel 写入 Excel 文件的示例代码,假设我们要将一个 List<User> 数据写入到 Excel 中:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.WriteSheet;
import java.util.ArrayList;
import java.util.List;class User {private String name;private int age;// 构造函数、Getter 和 Setter 方法省略,可根据实际情况补充完整public User(String name, int age) {this.name = name;this.age = age;}
}public class EasyExcelWriteExample {public static void main(String[] args) {List<User> userList = new ArrayList<>();userList.add(new User("张三", 25));userList.add(new User("李四", 30));String filePath = "output_excel_file.xlsx";  // 输出的 Excel 文件路径// 这里使用 EasyExcel 的 write 方法开始写入操作EasyExcel.write(filePath, User.class).sheet("用户信息表").doWrite(userList);}
}
注解

  • 在这个示例中,先定义了 User 类来表示要写入 Excel 的数据对象,包含 name 和 age 等属性(这里省略了部分常规的方法,实际使用中需补充完整)。
  • 在 EasyExcelWriteExample 类的 main 方法中,创建了一个 List<User> 集合并添加了一些示例数据。然后通过 EasyExcel.write 方法启动写入流程,传入输出文件路径和要写入的数据对象类型(User 类),接着指定 sheet 名称,最后调用 doWrite 方法并传入数据列表,这样就可以将数据成功写入到指定的 Excel 文件中啦。

四、EasyExcel 的性能与易用性优势

(一)性能优势

通过官方给出的效果图可以看到,EasyExcel 仅用 16M 内存在 23 秒内就能读取 75M(46W 行 25 列)的 Excel 文件,相比之下,POI 读取内存要用到 100M 往上了,读取时间更是没法比,可见在性能方面,EasyExcel 是遥遥领先呀。这得益于它基于事件机制逐行扫描数据,对数据逐行处理后,只要对象不被引用,就会被 JVM 快速垃圾回收,从而有效避免了 OOM 问题。

(二)易用性优势

使用 EasyExcel 进行文件的导入导出,相比 POI 的流程那可是简化了一大截呢,只要短短几行代码就可以轻松实现 Excel 的导入导出。这对于咱们开发者来说,简直太友好了,能大大提高开发效率呀。

五、EasyExcel 中的设计模式 个人理解

(一)工厂模式

EasyExcel 里的 EasyExcelFactory 类就运用了工厂模式哦。这个类拥有很多对应方法的不同入参的变体,最终目的都是为了根据不同的入参构造对应的 ExcelWriter/ReaderBuilder 对象,让代码调用起来简洁又美观呢。

(二)观察者模式(也叫发布 - 订阅模式)

通过事件监听器的机制,实现了类似观察者模式的效果。当 Excel 文件解析过程中遇到行、单元格等情况时会触发事件,然后通过自定义监听器来处理数据,各个监听器就像是观察者一样,等待着对应的事件发生然后执行相应的操作。

(三)模板方法模式

在解析和写入等流程中,定义了一些模板化的方法,不同的实现类可以按照这些模板去扩展和定制具体的业务逻辑,保证了整体流程的规范性和可扩展性。

(四)装饰器模式

在写样式等方面运用了装饰器模式,方便我们对 Excel 的样式等进行个性化的装饰和处理,让生成的 Excel 文件更加符合实际业务需求。

六、实际应用案例

在实际项目中,EasyExcel 发挥着巨大的作用呢。比如说,我曾经需要从 Excel 表格中批量导入用户信息到 MySQL 数据库里。如果使用 POI,不仅操作复杂,而且面对稍大一点的 Excel 文件就容易出现内存溢出问题。而选择 EasyExcel 后,它简单易用,性能又优越,更重要的是能够节约内存、解决大文件内存溢出问题。像一个 3M 的 excel 文件,用 POI sax 解析依然需要 100M 左右内存,改用 easyexcel 后可以降低到几 M,而且再大的 excel 文件也不会出现内存溢出啦。对于数据量小的文件,我采用同步模式一次性获取所有表格数据并存储到 List 中;对于数据量大的文件,则采用自定义 Listener 的方式异步逐行读取 Excel 并分批插入到数据库中,非常方便高效。

七、总结

总的来说,EasyExcel 作为一款强大的 Excel 处理组件,在 Apache POI 的基础上进行了优化和封装,无论是在性能、易用性还是功能扩展性上都有着出色的表现。通过运用多种设计模式,让它更加灵活且易于使用,在实际项目中能够帮我们轻松应对各种 Excel 文件的读写需求,大大提高了开发效率,也减少了因为内存溢出等问题带来的烦恼。希望大家在之后的开发工作中,如果遇到 Excel 处理相关的业务,不妨试试 EasyExcel 哦,相信它会给你带来意想不到的惊喜。

以上就是本次关于 EasyExcel 的全部内容分享啦,大家如果有任何疑问或者使用心得,欢迎在评论区留言交流呀。

觉得有用的话可以点点赞 (*/ω\*),支持一下。

如果愿意的话关注一下。会对你有更多的帮助。

每天都会不定时更新哦  >人<  。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477933.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Gradio学习笔记记录

安装指令&#xff1a;pip install gradio方法介绍 Interface》用于构建一些简单的页面&#xff0c;可以直接用这个指令搞定 形式》接收三个参数分别为处理函数、输入、输出三部分&#xff0c;呈现一般左/上为输入&#xff0c;右或下为输出 fn&#xff1a;将用户界面 &#xff0…

养老院管理系统+小程序项目需求分析文档

智慧综合养老服务平台是以业务为牵引、场景为驱动&#xff0c;围绕“老人”业务域&#xff0c;持续沉淀和打磨形成适应不同养老业务发展需要的业务能力&#xff0c;推动业务模式升级&#xff0c;为养老服务提供数字化解决方案&#xff0c;并依托实体站点与养老机构实现线上线下…

React的基本知识:事件监听器、Props和State的区分、改变state的方法、使用回调函数改变state、使用三元运算符改变state

这篇教学文章涵盖了大量的React基本知识。 包括&#xff1a; 事件监听器Props和State的区分改变state的方法使用回调函数改变state使用三元运算符改变state处理state中的数组处理state中的object条件渲染 &&条件渲染 三元运算符React中的forms 1. Event Listeners 在…

repmgr安装及常用运维指令

简介 repmgr 由 EDB 与其他个人和组织的贡献一起开发&#xff0c;安装部署相对较为简单 安装 repmgr官网上传对应的安装到服务器上 安装前/etc/hosts IP映射、始终同步、免密通信本文忽略 repmgr的安装相对较为简单,目前repmgr-5仅仅支持到postgresql-15 postgresql必要参数…

opencv-python 分离边缘粘连的物体(距离变换)

import cv2 import numpy as np# 读取图像&#xff0c;这里添加了判断图像是否读取成功的逻辑 img cv2.imread("./640.png") # 灰度图 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊 gray cv2.GaussianBlur(gray, (5, 5), 0) # 二值化 ret, binary cv2…

SATA接口不通分析案例分享

问题&#xff1a; 反馈有台NVR的某个接口SATA不通&#xff08;共有4个SATA接口&#xff0c;采用SATA HUB JMB575&#xff09;&#xff0c;挂载硬盘不上。 分析&#xff1a; 1、直接对换问题口SATA1与正常口SATA2的SATA数据线&#xff0c;SATA1口还是异常&#xff0c;挂在不上…

【Web前端】如何构建简单HTML表单?

HTML 表单是 Web 开发中非常重要的组成部分。它们是与用户交互的主要方式&#xff0c;能够收集用户输入的数据。表单的灵活性使它们成为 HTML 中最复杂的结构之一&#xff0c;但若使用正确的结构和元素&#xff0c;可以确保其可用性和无障碍性。 表单的基本结构 HTML 表单使用…

Flutter:AnimatedIcon图标动画,自定义Icon通过延时Interval,实现交错式动画

配置vsync&#xff0c;需要实现一下with SingleTickerProviderStateMixinclass _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{// late延迟初始化 AnimationControllerlate AnimationController _controller;overridevoid initStat…

PyQt学习笔记

一.PyQt5的安装 当我们安装好开发环境后&#xff0c;打开pycharm在其设置里面点击按钮自动安装即可。 安装完成后我们会在这里面看到这几个东西说明安装成功了。 二.PyQt5 GUI程序框架 1.一个简单的PyQt5应用程序 首先我们用pycharm创建一个demo.py的文件。 我们创建文件为s…

HTML5好看的音乐播放器多种风格(附源码)

文章目录 1.设计来源1.1 音乐播放器风格1效果1.2 音乐播放器风格2效果1.3 音乐播放器风格3效果1.4 音乐播放器风格4效果1.5 音乐播放器风格5效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&…

ReactPress(阮一峰推荐工具):一款基于Next.js的免费开源博客CMS系统

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 此项目是用于构建博客网站的&#xff0c;包含前台展示、管理后台和后端。 此项目是基于 React antd NestJS NextJS MySQL 的&#xff0c;项目已经开源&#xff0c;项目地址在 …

pytorch自定义算子导出onnx

文章目录 1、为什么要自定义算子&#xff1f;2、如何自定义算子3、自定义算子导出onnx4、example1、重写一个pytorch 自定义算子&#xff08;实现自定义激活函数&#xff09;2、现有算子上封装pytorch 自定义算子&#xff08;实现动态放大超分辨率模型&#xff09; 1、为什么要…

构建高效在线教育:SpringBoot课程管理系统

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理在线课程管理系统的相关信息成为必然。开发…

CSS3新特性——字体图标、2D、3D变换、过渡、动画、多列布局

目录 一、Web字体 二、字体图标 三、2D变换 1.位移 &#xff08;1&#xff09;浮动 &#xff08;2&#xff09;相对定位 &#xff08;3)绝对定位和固定定位 &#xff08;4&#xff09;位移 用位移实现盒子的水平垂直居中 2.缩放 利用缩放调整字体到12px以下&#xff…

python Flask指定IP和端口

from flask import Flask, request import uuidimport json import osapp Flask(__name__)app.route(/) def hello_world():return Hello, World!if __name__ __main__:app.run(host0.0.0.0, port5000)

linux ubuntu的脚本知

目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 N、其它可以参考的网址 脚本功能强大&#xff0c;用起来也…

C++(进阶) 第1章 继承

C&#xff08;进阶) 第1章 继承 文章目录 前言一、继承1.什么是继承2.继承的使用 二、继承方式1.private成员变量的&#xff08;3种继承方式&#xff09;继承2. private继承方式3.继承基类成员访问⽅式的变化 三、基类和派生类间的转换1.切片 四、 继承中的作⽤域1.隐藏规则&am…

Load-Balanced-Online-OJ(负载均衡式在线OJ)

负载均衡式在线OJ 前言1. 项目介绍2. 所用技术与环境所用技术栈开发环境 3. 项目宏观结构3.1 项目核心模块3.2 项目的宏观结构 4. comm公共模块4.1 日志&#xff08;log.hpp &#xff09;4.1.1 日志主要内容4.1.2 日志使用方式4.1.2 日志代码 4.2 工具&#xff08;util.hpp&…

c++->内部类 匿名对象

内部类&#xff1a;&#xff08;例如&#xff1a;b定义在a类中&#xff09; 注意事项&#xff1a; &#xff08;1&#xff09;内部类b可以直接使用外部类的static变量&#xff0c;但是并不属于外部类的友元&#xff01;&#xff01;&#xff01;&#xff01; #include <s…

C++ std::unique_ptr的使用及源码分析

目录 1.简介 2.使用方法 2.1.创建 unique_ptr 2.2.删除对象 2.3.转移所有权 2.4.自定义删除器 2.5.从函数返回 std::unique_ptr 2.6.将 std::unique_ptr 作为函数参数 3.适用场景 4.与原始指针的区别 5.优缺点 6.源码分析 6.1.构造函数 6.2.存储分析 6.3.默认删…