easyexcel解析excel文件原理解析

easyexcel是对poi基于事件机制解析excel文件的封装,poi又对sax解析xml文件进行了封装,所以要理解easyexcel解析的过程,就要先从sax开始。

  • sax解析xml文件
    使用sax解析xml文件时只要注册ContentHandler就行,sax是按行解析的,解析时会调用ContentHandler接口的钩子函数。
        SAXParserFactory factory = SAXParserFactory.newInstance();SAXParser saxParser = factory.newSAXParser();XMLReader xmlReader = saxParser.getXMLReader();xmlReader.setContentHandler(new DefaultHandler(){@Overridepublic void startElement(String uri, String localName, String qName, Attributes attributes){log.info("startElement-->>uri:{},localName:{},qName:{}",uri, localName,qName);}@Overridepublic void endElement(String uri, String localName, String qName){log.info("endElement-->>uri:{},localName:{},qName:{}",uri, localName,qName);}@Overridepublic void characters(char[] ch, int start, int length){log.info("characters-->>ch:{}, start:{}, length:{}", new String(ch,start,length), start, length);}});InputStream inputStream = new FileInputStream(Var.xmlFile);xmlReader.parse(new InputSource(inputStream));inputStream.close();
  • poi解析excel文件
    poi基于事件解析excel文件是对sax技术的封装,打开excel文件后,获取到所有sheet页,然后再类似xml逐行解析。
        OPCPackage opcPackage = OPCPackage.open(Var.excelFile, PackageAccess.READ);XSSFReader reader = new XSSFReader(opcPackage);XMLReader xmlReader = XMLReaderFactory.createXMLReader();xmlReader.setContentHandler(new SheetHandler());Iterator<InputStream> sheetsData = reader.getSheetsData();while (sheetsData.hasNext()){InputStream next = sheetsData.next();InputSource inputSource = new InputSource(next);xmlReader.parse(inputSource);next.close();}

在poi解析大excel文件的基础上出现了easyexcel,先看下easyexcel的用法,真的很easy啊,调静态方法read,传文件路径就行,获取到每行数据时,会帮我们封装成实体类,我们在invoke中进行处理即可。

EasyExcel.read(Var.excelFile, WfqData.class,new AnalysisEventListener<WfqData>() {@Overridepublic void invoke(WfqData data, AnalysisContext analysisContext) {// 按行读文件}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {log.info("处理文件结束");}}).headRowNumber(2)// 表头第二行,第一行是免责申明.doReadAll();

接下来开始看easyexcel的源码。read()方法中会构造ReadWorkbook对象,然后设置excel文件的File对象,注册读监听器。

    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;}

然后构造ExcelReader对象用来读取excel文件。

    public void doReadAll() {ExcelReader excelReader = build();excelReader.readAll();excelReader.finish();}

接下来根据excel文件类型确定解析器,

private void choiceExcelExecutor(ReadWorkbook readWorkbook) throws Exception {ExcelTypeEnum excelType = ExcelTypeEnum.valueOf(readWorkbook);switch (excelType) {case XLS:......break;case XLSX:XlsxReadContext xlsxReadContext = new DefaultXlsxReadContext(readWorkbook, ExcelTypeEnum.XLSX);analysisContext = xlsxReadContext;excelReadExecutor = new XlsxSaxAnalyser(xlsxReadContext, null);break;default:break;}}

然后设置一堆的配置,在解析的时候会用到,这些配置都保存在DefaultXlsxReadContext中,使用默认的解析事件处理器DefaultAnalysisEventProcessor,在解析excel的过程中会通过这个事件处理器调用我们注册进来的监听器方法。

    public AnalysisContextImpl(ReadWorkbook readWorkbook, ExcelTypeEnum actualExcelType) {if (readWorkbook == null) {throw new IllegalArgumentException("Workbook argument cannot be null");}switch (actualExcelType) {case XLS:readWorkbookHolder = new XlsReadWorkbookHolder(readWorkbook);break;case XLSX:readWorkbookHolder = new XlsxReadWorkbookHolder(readWorkbook);break;default:break;}currentReadHolder = readWorkbookHolder;analysisEventProcessor = new DefaultAnalysisEventProcessor();if (LOGGER.isDebugEnabled()) {LOGGER.debug("Initialization 'AnalysisContextImpl' complete");}}

解析前最重要的一环就是打开excel文档OPCPackage.open(xlsxReadWorkbookHolder.getFile());,这里就是我们熟悉的poi解析excel文件做的事情,然后获取每个要解析的sheet页,使用ReadSheet包装。

    public XlsxSaxAnalyser(XlsxReadContext xlsxReadContext, InputStream decryptedStream) throws Exception {this.xlsxReadContext = xlsxReadContext;// Initialize cacheXlsxReadWorkbookHolder xlsxReadWorkbookHolder = xlsxReadContext.xlsxReadWorkbookHolder();OPCPackage pkg = readOpcPackage(xlsxReadWorkbookHolder, decryptedStream);xlsxReadWorkbookHolder.setOpcPackage(pkg);ArrayList<PackagePart> packageParts = pkg.getPartsByContentType(XSSFRelation.SHARED_STRINGS.getContentType());if (!CollectionUtils.isEmpty(packageParts)) {PackagePart sharedStringsTablePackagePart = packageParts.get(0);// Specify default cachedefaultReadCache(xlsxReadWorkbookHolder, sharedStringsTablePackagePart);// Analysis sharedStringsTable.xmlanalysisSharedStringsTable(sharedStringsTablePackagePart.getInputStream(), xlsxReadWorkbookHolder);}XSSFReader xssfReader = new XSSFReader(pkg);analysisUse1904WindowDate(xssfReader, xlsxReadWorkbookHolder);xlsxReadWorkbookHolder.setStylesTable(xssfReader.getStylesTable());sheetList = new ArrayList<ReadSheet>();sheetMap = new HashMap<Integer, InputStream>();commentsTableMap = new HashMap<Integer, CommentsTable>();XSSFReader.SheetIterator ite = (XSSFReader.SheetIterator)xssfReader.getSheetsData();int index = 0;if (!ite.hasNext()) {throw new ExcelAnalysisException("Can not find any sheet!");}while (ite.hasNext()) {InputStream inputStream = ite.next();sheetList.add(new ReadSheet(index, ite.getSheetName()));sheetMap.put(index, inputStream);if (xlsxReadContext.readWorkbookHolder().getExtraReadSet().contains(CellExtraTypeEnum.COMMENT)) {CommentsTable commentsTable = ite.getSheetComments();if (null != commentsTable) {commentsTableMap.put(index, commentsTable);}}index++;}}

解析是在XlsxSaxAnalyser#execute方法中进行的,依次遍历获取的每个sheet页,保存下来后进行解析,就是poi解析excel文件的过程了。

    public void execute() {for (ReadSheet readSheet : sheetList) {readSheet = SheetUtils.match(readSheet, xlsxReadContext);if (readSheet != null) {xlsxReadContext.currentSheet(readSheet);parseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(xlsxReadContext));// Read commentsreadComments(readSheet);// The last sheet is readxlsxReadContext.analysisEventProcessor().endSheet(xlsxReadContext);}}}private 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);}...SAXParser saxParser = saxFactory.newSAXParser();XMLReader xmlReader = saxParser.getXMLReader();xmlReader.setContentHandler(handler);xmlReader.parse(inputSource);inputStream.close();}}

每次解析时都会回调XlsxRowHandler中的startElement、characters、endElement方法。
RowTagHandler#startElement()方法中会先获取当前要解析的行,然后与之前解析到的行进行对比,防止遗漏。

    public void startElement(XlsxReadContext xlsxReadContext, String name, Attributes attributes) {XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder();int rowIndex = PositionUtils.getRowByRowTagt(attributes.getValue(ExcelXmlConstants.ATTRIBUTE_R),xlsxReadSheetHolder.getRowIndex());Integer lastRowIndex = xlsxReadContext.readSheetHolder().getRowIndex();while (lastRowIndex + 1 < rowIndex) {xlsxReadContext.readRowHolder(new ReadRowHolder(lastRowIndex + 1, RowTypeEnum.EMPTY,xlsxReadSheetHolder.getGlobalConfiguration(), new LinkedHashMap<Integer, Cell>()));xlsxReadContext.analysisEventProcessor().endRow(xlsxReadContext);xlsxReadSheetHolder.setColumnIndex(null);xlsxReadSheetHolder.setCellMap(new LinkedHashMap<Integer, Cell>());lastRowIndex++;}xlsxReadSheetHolder.setRowIndex(rowIndex);}

解析完一行后,在AbstractCellValueTagHandler#endElement中会将当前行的列数和对应的值保存到cellMap(private Map<Integer, Cell> cellMap;)中。

public void endElement(XlsxReadContext xlsxReadContext, String name) {XlsxReadSheetHolder xlsxReadSheetHolder = xlsxReadContext.xlsxReadSheetHolder();CellData tempCellData = xlsxReadSheetHolder.getTempCellData();StringBuilder tempData = xlsxReadSheetHolder.getTempData();CellDataTypeEnum oldType = tempCellData.getType();switch (oldType) {case DIRECT_STRING:case STRING:case ERROR:tempCellData.setStringValue(tempData.toString());break;case BOOLEAN:tempCellData.setBooleanValue(BooleanUtils.valueOf(tempData.toString()));break;case NUMBER:case EMPTY:tempCellData.setType(CellDataTypeEnum.NUMBER);tempCellData.setNumberValue(new BigDecimal(tempData.toString()));break;default:throw new IllegalStateException("Cannot set values now");}// set string valuesetStringValue(xlsxReadContext);if (tempCellData.getStringValue() != null&& xlsxReadContext.currentReadHolder().globalConfiguration().getAutoTrim()) {tempCellData.setStringValue(tempCellData.getStringValue());}tempCellData.checkEmpty();xlsxReadSheetHolder.getCellMap().put(xlsxReadSheetHolder.getColumnIndex(), tempCellData);}

然后在RowTagHandler#endElement中调用xlsxReadContext.analysisEventProcessor().endRow(xlsxReadContext);方法进行处理,这里会调用DefaultAnalysisEventProcessor#dealData处理每行的实际数据,其实就是调用读监听器的invoke方法,也就是我们在EasyExcel.read中传的第三个参数。

    private void dealData(AnalysisContext analysisContext) {ReadRowHolder readRowHolder = analysisContext.readRowHolder();Map<Integer, CellData> cellDataMap = (Map)readRowHolder.getCellMap();readRowHolder.setCurrentRowAnalysisResult(cellDataMap);int rowIndex = readRowHolder.getRowIndex();int currentHeadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber();boolean isData = rowIndex >= currentHeadRowNumber;// Last head columnif (!isData && currentHeadRowNumber == rowIndex + 1) {buildHead(analysisContext, cellDataMap);}// Now is datafor (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();}}}

完成一个sheet页的解析,就会调用每个监听的doAfterAllAnalysed()方法。

    public void endSheet(AnalysisContext analysisContext) {for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) {readListener.doAfterAllAnalysed(analysisContext);}}

至此完成了对excel文件的解析。
总结下,easyexcel解析excel文件,先初始化用来解析excel文件用到的参数,比如文件路径、表头行、行数据class等,然后使用poi基于事件的机制进行解析。获取到数据后会把列索引和cell数据保存到map,然后调用每个监听器的invoke方法进行处理。有不对的地方请大神指出,欢迎大家一起讨论交流,共同进步。

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

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

相关文章

等保测评之主机测评详解(二级)

等保测评之主机测评详解&#xff08;二级&#xff09;服务器——Windows 身份鉴别: 测评项a&#xff09;&#xff1a; a&#xff09;应对登录的用户进行身份标识和鉴别&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别信息具有复杂度要求并定期更换&#xff1b; 整改方…

antd中Upload上传图片宽高限制以及上传文件的格式限制

项目中有一个需求&#xff0c;要上传轮播图&#xff0c;且有尺寸要求&#xff0c;所以就需要在上传图片的时候进行尺寸限制&#xff0c;使用了Upload组件&#xff0c;需要在组件的beforeUpload方法中进行限制。 定义一个上传前的方法&#xff0c;并且添加一个图片尺寸获取的方…

【Redis】Zset 数据类型

文章目录 常用命令zaddzcard & zcountzrange & zrevrangezpopmax & bzpopmaxzpopmin & bzpopminzrank & zrevrankzscore & zremzremrangebyrank & zremrangebyscorezincrby 多个集合间的交互命令交集 & zinterstore并集 & sunionstore 内部…

【声呐仿真】学习记录0.5-配置ssh远程连接docker、在docker中使用nvidia显卡

【声呐仿真】学习记录0.5-配置ssh远程连接docker、在docker中使用nvidia显卡 配置ssh远程连接docker1.端口映射2.配置ssh 在docker中使用nvidia显卡配置CUDA 注意&#xff1a;之前已经创建过容器的&#xff0c;需要打包成镜像&#xff0c;重新创建容器&#xff0c;因为要在创建…

【C++庖丁解牛】C++11---右值引用和移动语义

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1 左值引用和右值引用2 左…

第一个Spring Boot程序

目录 一、Spring Boot介绍 二、创建Spring Boot项目 1、插件安装&#xff08;专业版不需要&#xff09; 2、创建SpringBoot项目 &#xff08;1&#xff09;这里如果插件下载失败&#xff0c;解决方案&#xff1a; &#xff08;2&#xff09;项目启动失败&#xff0c;解决…

web测试基础知识

目录 web系统的基础 web概念(worldwideweb) 网络结构 发展 架构 B/S C/S P2P 工作原理 静态页面 动态页面 web客户端技术 浏览器的核心--渲染引擎 web服务器端技术 web服务器 应用服务器 集群环境 数据库 案例-URL 协议类型 主机名 端口 IP地址 分类 …

C#开发的全套成熟的LIS系统源码JavaScript+SQLserver 2012区域云LIS系统源码

C#开发的全套成熟的LIS系统源码JavaScriptSQLserver 2012区域云LIS系统源码 医院云LIS系统是一套成熟的实验室信息管理系统&#xff0c;目前已在多家三级级医院应用&#xff0c;并不断更新。云LIS系统是为病人为中心、以业务处理为基础、以提高检验科室管理水平和工作效率为目标…

贪心算法练习day.1

理论基础 贪心算法是一种常见的解决优化问题的方法&#xff0c;其基本思想就是在问题的每个决策阶段&#xff0c;都选择当前看起来最优的选择&#xff0c;即贪心地做出局部的最优决策&#xff0c;以此得到全局的最优解&#xff0c;例如在十张面额不同的钞票&#xff0c;让我们…

润申信息企业标准化管理系统 AddNewsHandler.ashx 任意用户创建漏洞复现

0x01 产品简介 润申信息科技企业标准化管理系统通过给客户提供各种灵活的标准法规信息化管理解决方案,帮助他们实现了高效的标准法规管理,完成个性化标准法规库的信息化建设。 0x02 漏洞概述 润申信息企业标准化管理系统 AddNewsHandler.ashx 接口处存在任意用户创建漏洞,…

Linux安装部署Tomcat

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Linux安装部署Tomcat //将tomcat压缩包解压到对…

python识别电脑是windows还是linux

代码实现 import osif os.name nt:print(当前操作系统是 Windows) elif os.name posix:print(当前操作系统是 Linux 或 Unix 类型的系统) else:print(未知的操作系统)

kubernetes中的副本控制器rc(replicationcontrollers)和rs(replicasets)

一、rc控制器replicationcontrollers rc控制器就是控制相同pod副本数量 使用rc控制器资源创建pod&#xff0c;设定创建pod资源的数量 1.1 案例 1.1.1、创建资源清单 [rootmaster rc-demo]# cat rc.yaml apiVersion: v1 kind: ReplicationController metadata: name: rc01 …

个人搭建alist网盘的经验记录备忘

1、搭建宝塔LINUX面板&#xff0c;安装Docker 2、添加仓库 3、从镜像拉取xhofe/alist:latest 4、添加容器 5、新建一个网站&#xff0c;别忘记申请个SSL证书&#xff0c;重要的是反向代理 6、新建个mysql数据库 7、修改alist数据库的链接地址&#xff0c;方便自己备份&a…

如何有效地进行汽车制造业文件共享,一文了解

随着数字化转变&#xff0c;企业的业务文件大多通过电子形式在内外部流转。这增加了外发文件数据泄露或被篡改的风险&#xff0c;如何保护外发文件安全已成为企业不容忽视的课题。其中汽车制造业是一个高度依赖文件共享与协作的行业&#xff0c;涉及设计图纸、技术文件、供应链…

Docker命令总结

一.Docker常用命令总结 1.镜像命令管理 指令描述ls列出镜像build构建镜像来自Dockerfilehistory查看历史镜像inspect显示一个或多个镜像的详细信息pull从镜像仓库拉取镜像push推送一个镜像到仓库rm移除一个或多个镜像prune一处未使用的的镜像&#xff0c;没有被标记或被任何容…

【用户投稿】Apache SeaTunnel 2.3.3+Web 1.0.0版本安装部署

项目概要 Apache SeaTunnel 是一个分布式、高性能、易扩展的数据集成平台&#xff0c;用于实时和离线数据处理,支持多种数据源之间的数据迁移和转换。 其中&#xff0c;Apache-seatunnel-web-1.0.0-bin.tar.gz和apache-seatunnel-2.3.3-bin.tar.gz代表了 Apache SeaTunnel Web…

RTT学习 开发环境搭建

添加文件到工程 BSP下的applications文件夹用于存放用户自己的应用代码&#xff0c;目前只有一个main.c文件&#xff0c;如果用户的应用代码不是很多&#xff0c;建议相关源文件都放在这个文件夹下面&#xff0c;在applications文件夹下新增两个简单的文件hello.c和hello.h。 …

什么是正向代理和反向代理

正向代理和反向代理是两种不同的代理服务器配置方式&#xff0c;它们在代理的方向和作用上有所不同。 一、正向代理&#xff08;Forward Proxy&#xff09; 代表客户端发送请求到其他服务器的代理服务器。客户端将请求发送给正向代理服务器&#xff0c;然后由正向代理服务器代…

如何使用OSI七层模型的思路进行Linux网络问题排障?

在运维工作中&#xff0c;我们可能经常遇到诸如服务器无法远程连接、网站无法访问等各种网络问题。此时你是否想过&#xff0c;我们常背的OSI七层模型&#xff0c;能在处理这样的实际问题中发挥什么样的作用呢&#xff1f; 基于OSI架构的方法论&#xff0c;我们可以使用自下而…