FastExcel/EasyExcel简介以及源码解析

简介

官网地址
GitHub地址
基于MIT协议

发展历史

由EasyExcel发展而来
2018/02/07:发布1.0.0
2019/09/17:发布2.0.0
2021/10/21:发布3.0.1
2024/06/18:发布4.0.0
2024/11/06:进入维护模式
2024/12/05:发布FastExcel1.0.0

主要特性

  • 高性能读写
  • 简单易用
  • 流式操作
  • 读取执行行数

技术原理

  • 内存优化:基于流式读取技术,不需要一次性将整个Excel文件加载到内存中,逐行或逐块读取数据。
  • 事件驱动模型:基于实现ReadListener接口处理读取操作。当读取到数据时,会触发接口中的方法,如invoke方法,支持开发者对每行数据进行即时处理。
  • 注解映射:用注解将Excel文件中的列与Java对象的属性进行映射。开发者能轻松地将Excel数据转换为Java对象,同时也支持反方向操作,将Java对象写入Excel

横向对比

jxlpoifastExcel
性能对比效率低细致和完整的操作支持,OOM的问题流式处理机制,仅逐行读写数据,极大地减少了内存消耗
API 易用性纯javaAPI,对中文支持非常好,操作简单较为底层和繁琐事件驱动模型,支持自定义注解进行数据映射
灵活性与扩展性格式只支持老版本报表能够应对各种定制化需求主要针对常规的读写场景进行了优化

源码解析

设计理念

  • API友好
  • 业务扩展性
  • 内存使用率

设计模式

  • 建造者模式
    • ExcelWriterBuilder
    • ExcelWriterSheetBuilder
    • ExcelWriterTableBuilder
    • ExcelReaderSheetBuilder
  • 观察者模式
    • ReadListener
    • AnalysisEventListener
  • 责任链设计模式
    • WorkbookHandlerExecutionChain
    • SheetHandlerExecutionChain
    • RowHandlerExecutionChain
    • CellHandlerExecutionChain
  • 桥接模式
    • ExcelAnalyser
    • ExcelReadExecutor
  • 工厂模式
    • FastExcelFactory
  • 模板模式
    • CellWriteHandler
    • RowTagHandler
    • CellTagHandler
    • BlankRecordHandler

Read Excel源码解析

// 代码示例
FastExcel.read(new File(fileNameTotal), BaseEntity.class, new UploadDataListener()).sheet().doRead();

Read Excel 初始化

// 运用门面模式,降低使用者的难度,有利于框架的传播
public class FastExcel extends FastExcelFactory {}
// 所有的read()方法都会返回ExcelReaderBuilder
public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) {ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();excelReaderBuilder.file(pathName);if (head != null) {excelReaderBuilder.head(head);}if (readListener != null) {excelReaderBuilder.registerReadListener(readListener);}return excelReaderBuilder;}
// 所有的sheet()方法都会到这里
// 其中new ExcelReaderSheetBuilder(build())的build()方法会执行前置的初始化操作
public ExcelReaderSheetBuilder sheet(Integer sheetNo, String sheetName) {ExcelReaderSheetBuilder excelReaderSheetBuilder = new ExcelReaderSheetBuilder(build());if (sheetNo != null) {excelReaderSheetBuilder.sheetNo(sheetNo);}if (sheetName != null) {excelReaderSheetBuilder.sheetName(sheetName);}return excelReaderSheetBuilder;}
// 到这里会根据文件类型来进行解析
private void choiceExcelExecutor(ReadWorkbook readWorkbook) throws Exception {
.....//  如果是xlsx格式,会进到这里case XLSX:XlsxReadContext xlsxReadContext = new DefaultXlsxReadContext(readWorkbook, ExcelTypeEnum.XLSX);analysisContext = xlsxReadContext;excelReadExecutor = new XlsxSaxAnalyser(xlsxReadContext, null);break;.....
}
// 会到这个构造方法里 执行readOpcPackage()方法  
// 此方法内会调用poi的OPCPackage包来打开excel文件获取文件内每个sheet的数据,包括comment和hyperlink,方便后续进行处理,至此初始化部分已完成public XlsxSaxAnalyser(XlsxReadContext xlsxReadContext, InputStream decryptedStream) throws Exception {this.xlsxReadContext = xlsxReadContext;XlsxReadWorkbookHolder xlsxReadWorkbookHolder = xlsxReadContext.xlsxReadWorkbookHolder();OPCPackage pkg = readOpcPackage(xlsxReadWorkbookHolder, decryptedStream);
.....
}

Read Excel 解析

回到前面sheet()方法,会返回ExcelReaderSheetBuilder
然后调用ExcelReaderSheetBuilder里的doRead()方法

    public void doRead() {if (excelReader == null) {throw new ExcelGenerateException("Must use 'FastExcelFactory.read().sheet()' to call this method");}excelReader.read(build());excelReader.finish();}// 最后会调用初始化生成的执行器public void analysis(List<ReadSheet> readSheetList, Boolean readAll) {try {if (!readAll && CollectionUtils.isEmpty(readSheetList)) {throw new IllegalArgumentException("Specify at least one read sheet.");}analysisContext.readWorkbookHolder().setParameterSheetDataList(readSheetList);analysisContext.readWorkbookHolder().setReadAll(readAll);try {excelReadExecutor.execute();.....            }
    public void execute() {for (ReadSheet readSheet : sheetList) {readSheet = SheetUtils.match(readSheet, xlsxReadContext);if (readSheet != null) {try {xlsxReadContext.currentSheet(readSheet);// 这里传入分页数据以及处理handlerparseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(xlsxReadContext));readComments(readSheet);} catch (ExcelAnalysisStopSheetException e) {if (log.isDebugEnabled()) {log.debug("Custom stop!", e);}}xlsxReadContext.analysisEventProcessor().endSheet(xlsxReadContext);}}}
// 这边会去调用SAX解析,解析时会执行xmlReader.setContentHandler(handler)里的方法
// SAX 解析。SAX 每次解析只在内存中加载 XML 文件的一小部分,即使针对较大的 XML 文件,它也不需要占用太多的内存,也不会存在内存溢出的问题。
// 优点: 1.采用事件驱动模式一段一段的来解析数据,占用内存小 2.只在读取数据时检查数据,不需要保存在内存中 3.效率和性能较高,能解析大于系统内存的文档当然 
// 缺点: 1.与 DOM 解析器相比,使用 SAX 解析器读取 XML 文件时,解析逻辑比较复杂 2.同时无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持 XPathprivate void parseXmlSource(InputStream inputStream, ContentHandler handler) {InputSource inputSource = new InputSource(inputStream);try {SAXParserFactory saxFactory;String xlsxSAXParserFactoryName = xlsxReadContext.xlsxReadWorkbookHolder().getSaxParserFactoryName();if (StringUtils.isEmpty(xlsxSAXParserFactoryName)) {saxFactory = SAXParserFactory.newInstance();} else {saxFactory = SAXParserFactory.newInstance(xlsxSAXParserFactoryName, null);}try {saxFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);} catch (Throwable ignore) {}try {saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);} catch (Throwable ignore) {}try {saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);} catch (Throwable ignore) {}SAXParser saxParser = saxFactory.newSAXParser();XMLReader xmlReader = saxParser.getXMLReader();xmlReader.setContentHandler(handler);xmlReader.parse(inputSource);inputStream.close();} catch (IOException | ParserConfigurationException | SAXException e) {throw new ExcelAnalysisException(e);} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {throw new ExcelAnalysisException("Can not close 'inputStream'!");}}}}

我们查看传入的Handler,查看RowTagHandler,继承了AbstractXlsxTagHandler

// 会在操作前后执行动作@Overridepublic void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) {}@Overridepublic void endElement(XlsxReadContext xlsxReadContext, String name) {......// 在这里调用了endRow()方法xlsxReadContext.analysisEventProcessor().endRow(xlsxReadContext);......}
	// 会在dealData()方法里处理数据@Overridepublic void endRow(AnalysisContext analysisContext) {if (RowTypeEnum.EMPTY.equals(analysisContext.readRowHolder().getRowType())) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Empty row!");}if (analysisContext.readWorkbookHolder().getIgnoreEmptyRow()) {return;}}dealData(analysisContext);}
    private void dealData(AnalysisContext analysisContext) {ReadRowHolder readRowHolder = analysisContext.readRowHolder();Map<Integer, ReadCellData<?>> cellDataMap = (Map)readRowHolder.getCellMap();readRowHolder.setCurrentRowAnalysisResult(cellDataMap);int rowIndex = readRowHolder.getRowIndex();int currentHeadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber();boolean isData = rowIndex >= currentHeadRowNumber;if (!isData && currentHeadRowNumber == rowIndex + 1) {buildHead(analysisContext, cellDataMap);}// 到这边就会回调监听器生成对象并且到我们自定义的监听器处理数据for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) {try {if (isData) {readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext);} else {readListener.invokeHead(cellDataMap, analysisContext);}} catch (Exception e) {onException(analysisContext, e);break;}if (!readListener.hasNext(analysisContext)) {throw new ExcelAnalysisStopException();}}}

点击invoke()到ModelBuildEventListener生成对象监听器里,查看buildUserModel()方法,可以看到此方法在ReadSheetHolder(注解)获取对象信息,通过反射创建对象,然后对各个属性进行赋值,只会在处理到这行数据的时候封装成对应的java对象。
这也就是为什么fastexcel占用内存少的原因

    private Object buildUserModel(Map<Integer, ReadCellData<?>> cellDataMap, ReadSheetHolder readSheetHolder,AnalysisContext context) {ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty();Object resultModel;try {resultModel = excelReadHeadProperty.getHeadClazz().newInstance();} catch (Exception e) {throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0,new ReadCellData<>(CellDataTypeEnum.EMPTY), null,"Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e);}Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();BeanMap dataMap = BeanMapUtils.create(resultModel);for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {Integer index = entry.getKey();Head head = entry.getValue();String fieldName = head.getFieldName();if (!cellDataMap.containsKey(index)) {continue;}ReadCellData<?> cellData = cellDataMap.get(index);Object value = ConverterUtils.convertToJavaObject(cellData, head.getField(),ClassUtils.declaredExcelContentProperty(dataMap, readSheetHolder.excelReadHeadProperty().getHeadClazz(),fieldName, readSheetHolder), readSheetHolder.converterMap(), context,context.readRowHolder().getRowIndex(), index);if (value != null) {dataMap.put(fieldName, value);}}return resultModel;}

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

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

相关文章

React学习笔记10

一、Redux与React-提交action传参 需求&#xff1a;组件中有两个按钮&#xff0c;点击add to 10和add to 20将count的值修改到对应的数字&#xff0c;目标count值是在组件中传递过去的&#xff0c;需要提交action的时候传递参数 实现思路&#xff1a;在reducers的同步修改方法中…

Docker概念与架构

文章目录 概念docker与虚拟机的差异docker的作用docker容器虚拟化 与 传统虚拟机比较 Docker 架构 概念 Docker 是一个开源的应用容器引擎。诞生于 2013 年初&#xff0c;基于 Go 语言实现。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xf…

HarmonyOS Next~应用开发入门:从架构认知到HelloWorld实战

HarmonyOS应用开发入门&#xff1a;从架构认知到HelloWorld实战 一、HarmonyOS架构解析 1.1 分布式能力三要素 &#xff08;1&#xff09;软总线&#xff08;SoftBus&#xff09; HarmonyOS的核心神经中枢&#xff0c;通过统一的分布式通信协议实现设备间的自动发现和组网。…

相控阵扫盲

下图展示天线增益 在仰角为0度的情况下随着方位角的变化而变化。需要注意到的是在天线视轴方向上的高增益主瓣上还有几个低增益旁瓣 阵列因子乘以新的阵元方向图会形成指向性更强的波速

[QT]开发全解析:从概念到实战

文章目录 Qt 框架入门与应用开发指南一、Qt 框架概述1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt 的应用场景1.7 Qt 的成功案例 二、Qt 的开发工具概述Qt CreatorVisual StudioEclipse 三、认识 Qt Creator3.1 Qt Creator 概览3.2 使用 Qt C…

LeetCode 718.最长重复子数组(动态规划,Python)

给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。 示例 2&#xff1a; 输…

从厨电模范到数字先锋,看永洪科技如何助力方太集团开启数字新征程

在数字化洪流席卷全球的宏大背景下&#xff0c;企业转型升级的紧迫性与重要性日益凸显&#xff0c;成为驱动行业进步的关键引擎。在这一波澜壮阔的转型浪潮中&#xff0c;方太集团——厨电领域的璀璨明珠&#xff0c;以其前瞻性的战略视野和不懈的创新精神&#xff0c;携手数据…

C++11中atomic

C11中atomic 在C中&#xff0c;std::atomic 是一个非常重要的工具&#xff0c;主要用于实现线程安全的操作。它属于C11标准引入的 <atomic> 头文件的一部分&#xff0c;用于处理多线程环境下的原子操作。以下是 std::atomic 的主要作用和特点&#xff1a; 1. 保证操作的…

尚庭公寓项目记录

数据库准备 保留图像时&#xff0c;保存图像地址就可以数据表不是越多越好&#xff0c;可以用中间表来实现俩个表之间的联立这样方便查数据但是却带来性能问题而减少表的jion但是提高性能&#xff0c;以冗余来换去性能采用MySQL&#xff0c;InnoDB存储引擎物理删除和逻辑删除逻…

unity6 打包webgl注意事项

webgl使用资源需要异步加载 使用localization插件时要注意&#xff0c;webgl不支持WaitForCompletion&#xff0c;LocalizationSettings.InitializationOperation和LocalizationSettings.StringDatabase.GetTable都不能用 web里想要看到具体的报错信息调试开启这两个&#xf…

wxWidgets GUI 跨平台 入门学习笔记

准备 参考 https://wiki.wxwidgets.org/Microsoft_Visual_C_NuGethttps://wiki.wxwidgets.org/Tools#Rapid_Application_Development_.2F_GUI_Buildershttps://docs.wxwidgets.org/3.2/https://docs.wxwidgets.org/latest/overview_helloworld.htmlhttps://wizardforcel.gitb…

C++20 中使用括号进行聚合初始化:新特性与实践指南

文章目录 1. 聚合初始化简介2. C20 中的括号聚合初始化2.1 指定初始化器&#xff08;Designated Initializers&#xff09;2.2 嵌套聚合初始化 3. 使用括号初始化数组4. 注意事项5. 实际应用场景6. 总结 在 C20 中&#xff0c;聚合初始化&#xff08;Aggregate Initialization&…

TomcatServlet

https://www.bilibili.com/video/BV1UN411x7xe tomcat tomcat 架构图&#xff0c;与 jre&#xff0c;应用程序之前的关系 安装使用 tomcat 10 开始&#xff0c;api 从 javax.* 转为使用 jakarta.*&#xff0c;需要至少使用 jdk 11 cmd 中默认 gbk 编码&#xff0c;解决控制…

android接入rocketmq

一 前言 RocketMQ 作为一个功能强大的消息队列系统&#xff0c;不仅支持基本的消息发布与订阅&#xff0c;还提供了顺序消息、延时消息、事务消息等高级功能&#xff0c;适应了复杂的分布式系统需求。其高可用性架构、多副本机制、完善的运维管理工具&#xff0c;以及安全控制…

有关Java中的集合(1):List<T>和Set<T>

学习目标 核心掌握List集合了解Set集合 1.List<T> ● java.util.List。有序列表。 ● List集合元素的特点&#xff1a;有序表示存取有序&#xff08;因为有索引&#xff09;而且可以重复 ● List常用实现类&#xff1a; ArrayList、LinkedList、Vector等 1.1 常用方法…

DeepSeek+Graphrag检索增强

用于增强的文章为一篇机器学习的文章&#xff0c;以及本人自己的论文 对于此感兴趣的可私聊我&#xff0c;过多细节不便展示 实现方法 图构建 数据收集&#xff1a;收集与检索相关的各种数据&#xff0c;如文本、图像、元数据等。实体识别和关系抽取&#xff1a;从收集的数据…

利用opencv_python(pdf2image、poppler)将pdf每页转为图片

1、安装依赖pdf2image pip install pdf2image 运行.py报错&#xff0c;因为缺少了poppler支持。 2、安装pdf2image的依赖poppler 以上命令直接报错。 改为手工下载&#xff1a; github: Releases oschwartz10612/poppler-windows GitHub 百度网盘&#xff1a; 百度网盘…

C# Unity 面向对象补全计划 之 [反射]自动处理带有自定义[特性]的类

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 有一些插件就是利用本篇的方法做"自动"处理的 目录 1.情景: 2.介绍与举例: 自定义特性API与使用 反射搜索自定义API 3.优化 4.处理带有自定义特性的类…

AI-Deepseek + PPT

01--Deepseek提问 首先去Deepseek问一个问题&#xff1a; Deepseek的回答&#xff1a; 在汽车CAN总线通信中&#xff0c;DBC文件里的信号处理&#xff08;如初始值、系数、偏移&#xff09;主要是为了 将原始二进制数据转换为实际物理值&#xff0c;确保不同电子控制单元&…

SpringMVC控制器定义:@Controller注解详解

文章目录 引言一、Controller注解基础二、RequestMapping与请求映射三、参数绑定与数据校验四、RestController与RESTful API五、控制器建议与全局处理六、控制器测试策略总结 引言 在SpringMVC框架中&#xff0c;控制器(Controller)是整个Web应用的核心组件&#xff0c;负责处…