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(){
            @Override
            public void startElement(String uri, String localName, String qName, Attributes attributes){
                log.info("startElement-->>uri:{},localName:{},qName:{}",uri, localName,qName);
            }
            @Override
            public void endElement(String uri, String localName, String qName){
                log.info("endElement-->>uri:{},localName:{},qName:{}",uri, localName,qName);
            }
            @Override
            public 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>() {
                        @Override
                        public void invoke(WfqData data, AnalysisContext analysisContext) {
                            // 按行读文件
                        }

                        @Override
                        public 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 cache
        XlsxReadWorkbookHolder 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 cache
            defaultReadCache(xlsxReadWorkbookHolder, sharedStringsTablePackagePart);

            // Analysis sharedStringsTable.xml
            analysisSharedStringsTable(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 comments
                readComments(readSheet);
                // The last sheet is read
                xlsxReadContext.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 value
        setStringValue(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 column
        if (!isData && currentHeadRowNumber == rowIndex + 1) {
            buildHead(analysisContext, cellDataMap);
        }
        // Now is data
        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();
            }
        }
    }

完成一个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.mfbz.cn/a/568614.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

结构型设计模式

享元模式 享元模式&#xff08;Flyweight Pattern&#xff09;是一种用于性能优化的设计模式&#xff0c;它通过共享尽可能多的相似对象来减少内存使用&#xff0c;尤其是在大量对象的情况下非常有效。这个模式是在对象数量多而对象状态大部分可共享的情况下实现的。 享元模式…

【C 数据结构】树

文章目录 【 1. 基本原理 】1.1 子树、空树1.2 有序数、无序树1.3 森林 【 2. 结点 】【 3. 度、层次、深度 】 【 1. 基本原理 】 树结构是一种 非线性存储结构&#xff0c;存储的是具有 一对多 关系的数据元素的集合。一对多 如下图中的左图所示&#xff0c;对于数据 A 来…

【webrtc】Chrome和Firefox在SDP协商过程中,针对localhost的不同处理

内网下chrome端webrtc协商失败 现象 我有一个webrtc服务器在局域网内&#xff0c;使用chrome浏览器访问时&#xff0c;发现webrtc在做媒体协商时失败。 具体表现是&#xff0c;在交换sdp后&#xff0c;ice的状态是oniceconnectionstatechange: failed 但是换成Firefox浏览器…

html接入腾讯地图

1.申请key key申请地址&#xff1a;https://lbs.qq.com/dev/console/application/mine 官方文档 https://lbs.qq.com/webApi/javascriptGL/glGuide/glBasic 2.html接入示例 <!DOCTYPE html> <html lang"en"> <head><meta charset"U…

全国青少年劳动技能与智能设计大赛安徽省赛——庐江县师资培训活动圆满举行

为贯彻落实科教兴国的国家战略目标&#xff0c;根据《教育部办公厅关于公布 2022—2025 学年面向中小学生的全国性竞赛活动》的相关通知。为了提升教师在劳动技能与智能设计领域的教学与指导能力&#xff0c;为即将到来的省级大赛做好充分准备。4月18日&#xff0c;一场由庐江县…

维基百科、百度百科和搜狗百科词条的创建流程

随着网络的发展&#xff0c;百度百科、搜狗百科、维基百科等百科网站已经成为大众获取知识的重要途径。因为百科具有得天独厚的平台优势&#xff0c;百科上的信息可信度高&#xff0c;权威性强。所以百科平台也成为商家的必争之地。这里小马识途聊聊如何创建百度百科、搜狗百科…

GPT与GAN结合生成图像——VQGAN原理解析

1、前言 这篇文章&#xff0c;我们讲VQ_GAN&#xff0c;这是一个将特征向量离散化的模型&#xff0c;其效果相当不错&#xff0c;搭配Transformer&#xff08;GPT&#xff09;或者CLIP使用&#xff0c;达到的效果在当时可谓是令人拍案叫绝&#xff01; 原论文&#xff1a;Tam…

LTD271次升级 | 网站/小程序可设访问IP的黑白名单 • 官微中心支持PDF等办公文件预览与并分享 • 订单退款显示更详尽明细

1、新增IP访问限制功能&#xff1b; 2、订单新增交易号显示与退款明细显示&#xff1b; 3、自定义地址增加四级地区&#xff1b; 4、Android版App优化文件功能&#xff1b; 5、已知问题修复与优化&#xff1b; 01 官微中心 1) 新增IP限制访问功能 允许或者禁止某些 IP 或…

uniapp项目中禁止横屏 ,app不要自动旋转 -,保持竖屏,uniapp取消重力感应

uniapp项目中禁止横屏 &#xff0c;app不要自动旋转 -&#xff0c;保持竖屏&#xff0c;uniapp取消重力感应 1.适用于移动端&#xff0c;安卓和IOS&#xff0c;当即使手机打开了自动旋转的按钮&#xff0c;设置如下的代码后&#xff0c;页面依旧保持竖屏。 步骤一&#xff1a…

【深度学习】yolo-World,数据标注,zeroshot,目标检测

仓库&#xff1a;https://github.com/AILab-CVC/YOLO-World 下载权重&#xff1a; 仓库下载和环境设置 下载仓库&#xff1a;使用以下命令从 GitHub 上克隆仓库&#xff1a; git clone --recursive https://github.com/AILab-CVC/YOLO-World.git创建并激活环境&#xff1a…

程序猿成长之路之数据挖掘篇——朴素贝叶斯

朴素贝叶斯是数据挖掘分类的基础&#xff0c;本篇文章将介绍一下朴素贝叶斯算法 情景再现 以挑选西瓜为例&#xff0c;西瓜的色泽、瓜蒂、敲响声音、触感、脐部等特征都会影响到西瓜的好坏。那么我们怎么样可以挑选出一个好的西瓜呢&#xff1f; 分析过程 既然挑选西瓜有多个…

DaPy:实现数据分析与处理

DaPy&#xff1a;实现数据分析与处理 DaPy是一个用于数据分析和处理的Python库&#xff0c;它提供了一系列强大的工具和功能&#xff0c;使开发者能够高效地进行数据清洗、转换和分析。本文将深入解析DaPy库的特点、功能以及使用示例&#xff0c;帮助读者了解如何利用DaPy库处理…

贪心算法在单位时间任务调度问题中的应用

贪心算法在单位时间任务调度问题中的应用 一、引言二、问题描述与算法设计三、算法证明四、算法实现与效率分析五、C语言实现示例六、结论 一、引言 单位时间任务调度问题是一类经典的优化问题&#xff0c;旨在分配任务到不同的时间槽中&#xff0c;使得某种性能指标达到最优。…

【QT进阶】Qt http编程之实现websocket server服务器端

往期回顾 【QT进阶】Qt http编程之json解析的简单介绍-CSDN博客 【QT进阶】Qt http编程之nlohmann json库使用的简单介绍-CSDN博客 【QT进阶】Qt http编程之websocket的简单介绍-CSDN博客 【QT进阶】Qt http编程之实现websocket server服务器端 一、最终效果 通过ip地址和端口…

万界星空科技电机行业MES+商业电机行业开源MES+项目合作

要得出mes系统解决方案在机电行业的应用范围&#xff0c;我们先来看一下传统机电行业的管理难题&#xff1a; 1、 产品标准化程度较低&#xff0c;制造工艺复杂&#xff0c;生产周期较长&#xff0c;产品质量不稳定&#xff1b; 2、 自动化程度低&#xff0c;大多数工序以手工…

【视频异常检测】Open-Vocabulary Video Anomaly Detection 论文阅读

Open-Vocabulary Video Anomaly Detection 论文阅读 AbstractMethod3.1. Overall Framework3.2. Temporal Adapter Module3.3. Semantic Knowledge Injection Module3.4. Novel Anomaly Synthesis Module3.5. Objective Functions3.5.1 Training stage without pseudo anomaly …

电子信息制造工厂5G智能制造数字孪生可视化平台,推进数字化转型

电子信息制造工厂5G智能制造数字孪生可视化平台&#xff0c;推进数字化转型。5G智能制造数字孪生可视化平台利用5G网络的高速、低延迟特性&#xff0c;结合数字孪生技术和可视化界面&#xff0c;为电子信息制造工厂提供了一种全新的生产管理模式。不仅提升生产效率&#xff0c;…

设计模式(三):抽象工厂模式

设计模式&#xff08;三&#xff09;&#xff1a;抽象工厂模式 1. 抽象工厂模式的介绍2. 抽象工厂模式的类图3. 抽象工厂模式的实现3.1 创建摩托车的接口3.2 创建摩托车的具体实现3.3 创建汽车的接口3.4 创建汽车的具体产品3.5 创建抽象工厂3.6 创建具体工厂3.7 创建工厂生成器…

Fisher判别示例:鸢尾花(iris)数据(R)

先读取iris数据&#xff0c;再用程序包MASS&#xff08;记得要在使用MASS前下载好该程序包&#xff09;中的线性函数lda()作判别分析&#xff1a; data(iris) #读入数据 iris #展示数据 attach(iris) #用变量名绑定对应数据 library(MASS) #加载MASS程序包 ldlda(Species~…

《ElementPlus 与 ElementUI 差异集合》el-select 显示下拉列表在 Cesium 场景中无法监听关闭

前言 仅在 Element UI 时有此问题&#xff0c;Element Plus 由于内部结构差异较大&#xff0c;不存在此问题。详见《el-select 差异点&#xff0c;如&#xff1a;高、宽、body插入等》&#xff1b; 问题 点击空白处&#xff0c;下拉列表可监听并关闭&#xff1b;但在 Cesium…
最新文章