EasyExcel-读Excel-不创建对象的读-合并单元格的处理

news2024/11/13 21:21:45

EasyExcel官方文档

这几天需要读取excel的内容,但是excel中存在多个sheet页,每个sheet页的标题不同,数据不同,而且多个excel文件。决定使用easyexcel处理,但是感觉无法使用对象接收exceld的数据,所以决定使用“不创建对象的读”来接受数据。

测试使用过程中,发现如果存在合并单元的情况,读取出来的数据不是自己想要的,直接上图说明,假如有一个excel文件,存在一个sheet页,数据如下:

在这里插入图片描述
可以看出,存在几个地方合并单元的数据,如果什么都不处理,直接读取当前数据,结果如下:
在这里插入图片描述
这并不是我想要的,网上查询看到有人说,easyexcel只有合并单元格的左上角单元格才有值,其他的单元格没有值,看来确实是的。

我的想法就是根据官方文档,获取到excel中的所有行数据,以及合并单元格的数据信息,最后整合这两部分数据,更新行数据。

具体的easyexcel用法自行参考官方,代码是我自己写的,绝对不是随便copy过来的,亲测有效。

我写代码的时候是按照多个sheet页来的(返回多个sheet页的所有数据),但实际测试的excel只创建了一个sheet页。因为我只是测试这个过程,并没有涉及实际的业务,后面会根据这个demo来实现实际业务中的需求。

具体实现如下:

package demo.jiangkd.easyexcel.controller;

import demo.jiangkd.easyexcel.service.EasyExcelMergeDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

/**
 * @author jiangkd
 * @date 2024/8/9 9:16
 */
@RequiredArgsConstructor
@RequestMapping("/easyexcel")
@RestController
public class EasyExcelDemoController {

    private final EasyExcelMergeDemoService easyExcelMergeDemoService;

    /**
     * 如果excel中带有合并单元格, 又是不创建对象的读取
     *
     * @param filename
     */
    @GetMapping("/merge/demo")
    public List<Map<Integer, String>> EasyExcel(String filename) {
        //
        return easyExcelMergeDemoService.mergeDemo(filename);
    }
}
package demo.jiangkd.easyexcel.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.metadata.ReadSheet;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author jiangkd
 * @date 2024/8/9 14:49
 */
@Slf4j
@Service
public class EasyExcelMergeDemoService {

    public List<Map<Integer, String>> mergeDemo(String filename) {

        // 所有的sheet, 不用管excel有几个sheet, 数据全部返回
        @Cleanup final ExcelReader build = EasyExcel.read(filename).build();
        final List<ReadSheet> readSheets = build.excelExecutor().sheetList();

        final List<Map<Integer, String>> datas = new ArrayList<>();

        final int headRowNumber = 0;

        @Cleanup
        ExcelReader excelReader = EasyExcel.read(filename)
                // 从sheet页的第一行开始读取,默认是从第二行开始的
                .headRowNumber(headRowNumber)
                // 需要读取合并单元格信息 默认不读取
                .extraRead(CellExtraTypeEnum.MERGE)
                .build();

        readSheets.forEach(readSheet -> {

            log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 开始读取sheet, {}, {}", readSheet.getSheetNo(),
                    readSheet.getSheetName());

            final NoModelMergeDataListener noModelMergeDataListener = new NoModelMergeDataListener(headRowNumber);

            final ReadSheet sheet = EasyExcel.readSheet(readSheet.getSheetNo())
                    .registerReadListener(noModelMergeDataListener)
                    .build();

            excelReader.read(sheet);

            final List<CellExtra> extraMergeInfoList = noModelMergeDataListener.getExtraMergeInfoList();
            final List<Map<Integer, String>> excelDatas = noModelMergeDataListener.getDatas();
            if (CollectionUtils.isEmpty(extraMergeInfoList)) {
                datas.addAll(excelDatas);
            } else {
                datas.addAll(this.explainMergeData(excelDatas, extraMergeInfoList));
            }

        });

        return datas;

    }

    private List<Map<Integer, String>> explainMergeData(List<Map<Integer, String>> excelDatas,
                                                        List<CellExtra> extraMergeInfoList) {

        // easyexcel默认处理合并单元格, 只有左上角的有值, 其他的没值

        // 先找出合并的单元格的值
        for (CellExtra cellExtra : extraMergeInfoList) {

            // 通过cellExtra的rowIndex和columnIndex来确定该单元格的值
            final Integer rowIndex = cellExtra.getRowIndex();
            final Integer columnIndex = cellExtra.getColumnIndex();

            // 通过rowIndex确定第几行数据
            final Map<Integer, String> rowData = excelDatas.get(rowIndex);
            // 通过columnIndex确定这一行数据的第几列
            final Set<Map.Entry<Integer, String>> entries = rowData.entrySet();
            final List<Map.Entry<Integer, String>> entriesList = new ArrayList<>(entries);
            final Map.Entry<Integer, String> entry = entriesList.get(columnIndex);
            final String value = entry.getValue();

            log.info("当前合并的单元格的值是:{}", value);

            // 获取合并单元格的范围Index数据, 也就是跨越了那些单元格
            final Integer firstRowIndex = cellExtra.getFirstRowIndex();
            final Integer lastRowIndex = cellExtra.getLastRowIndex();
            final Integer firstColumnIndex = cellExtra.getFirstColumnIndex();
            final Integer lastColumnIndex = cellExtra.getLastColumnIndex();

            // 纵使easyexcel合并单元只有左上角的单元格有值, 为了方便, 这里直接把范围单元格的值全部替换掉为value

            // 获取要处理的行数据都有哪些, 也就是[firstRowIndex, lastRowIndex]的行数据
            final List<Map<Integer, String>> mergeRowDatas = new ArrayList<>();

            final int size = excelDatas.size();
            for (int i = 0; i < size; i++) {
                if (firstRowIndex <= i && i <= lastRowIndex) {
                    mergeRowDatas.add(excelDatas.get(i));
                }
            }

            // 确定了哪些行数据中存在合并的单元格, 接下来更新这些行数据的那些列
            for (Map<Integer, String> mergeRowData : mergeRowDatas) {

                // 这一行数据的所有列
                final Set<Map.Entry<Integer, String>> mergeRowDataEntries = mergeRowData.entrySet();
                final List<Map.Entry<Integer, String>> mergeRowDataEntriyList = new ArrayList<>(mergeRowDataEntries);

                // 确定列并且更新列的值
                final int columnSize = mergeRowDataEntriyList.size();
                for (int i = 0; i < columnSize; i++) {
                    if (firstColumnIndex <= i && i <= lastColumnIndex) {
                        final Map.Entry<Integer, String> columnEntry = mergeRowDataEntriyList.get(i);
                        columnEntry.setValue(value);
                    }
                }

            }

        }

        return excelDatas;

    }

}
package demo.jiangkd.easyexcel.service;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author jiangkd
 * @date 2024/8/9 14:52
 */
@Slf4j
public class NoModelMergeDataListener implements ReadListener<Map<Integer, String>> {

    /**
     * excel解析的数据
     */
    final List<Map<Integer, String>> datas = new ArrayList<>();

    /**
     * 正文起始行
     */
    private Integer headRowNumber;

    /**
     * 合并单元格
     */
    private List<CellExtra> extraMergeInfoList = new ArrayList<>();

    public List<Map<Integer, String>> getDatas() {
        return datas;
    }

    public List<CellExtra> getExtraMergeInfoList() {
        return extraMergeInfoList;
    }

    public NoModelMergeDataListener(Integer headRowNumber) {
        this.headRowNumber = headRowNumber;
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        //
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        datas.add(data);
    }

    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        //
        log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
        if (Objects.requireNonNull(extra.getType()) == CellExtraTypeEnum.MERGE) {
            log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                    extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                    extra.getLastColumnIndex());

            if (extra.getRowIndex() >= headRowNumber) {
                extraMergeInfoList.add(extra);
            }
        }

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //
        log.info("======================================");
        log.info("所有数据解析完成!");
        log.info("======================================");
    }
}

针对如上代码,说明几点:

  1. 里面没有说明注释的代码,翻看easyexcel官方文档就会明白了。
  2. 读取了excel中的所有sheet页,循环sheet读取数据,最后一块全部返回,如果你只读取第一个sheet页,略改代码即可(或者直接不改)。
  3. 从监听中获取excel的行数据和合并单元格的数据信息。
  4. 合并单元格的信息主要就是单元格的位置,行列的index,可以确定那些范围内的单元格是合并的。

然后再次执行,结果如下:
在这里插入图片描述
现在数据是我想要的了。

马上就要下班了,文章写的有点乱,格式不是很清晰,但是代码其实没有很多,结合官方文档,很容易理解的。

至于代码中的CellExtra,以及get出来的几个Index数据,执行代码测试的时候,查看控制台打印结果,对比excel,你就明白了。

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

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

相关文章

使用 Node.js 模拟执行 JavaScript

准备工作 正确安装好 Node.js ,安装好之后&#xff0c;能正常使用 node 和 npm 两个命令 模拟执行 关于案例分析 写文章-CSDN创作中心 这里就不做分析了&#xff0c;直接使用 我们的目的是&#xff1a; 使用 node.js 加载 Crypto 库&#xff0c; 并执行 getToken 方法 …

Linux驱动开发基础(LED驱动)

所学来自百问网 目录 1. LED原理 2. 普适的GPIO引脚操作方法 2.1 GPIO模块的一般结构 2.2 GPIO框图 2.3 寄存器的操作 2.3.1 一般的操作方式 2.3.2 高效的操作方式 3. 基于IMX6UL_6ULL的GPIO操作方法 3.1 GPIO框图 3.2 CCM 3.3 IOMUXC 3.4 GPIO模块内部 3.5 读写…

软件评审-需求评审、设计评审、编码评审、测试评审(原件)

1.需求规格说明评审报告 2.系统设计评审报告 3.编码与测试评审报告 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0…

工业大数据来自哪里?大数据技术如何助力制造企业数字化转型?

信息技术的迅猛发展正在重塑我们的世界&#xff0c;不仅改变了技术本身&#xff0c;也深刻影响了全球市场和人们的工作与生活方式。在工业生产这一关键领域&#xff0c;高性价比、长续航的微型传感器的诞生&#xff0c;以及物联网等新一代网络技术的兴起&#xff0c;正赋予无数…

【C语言篇】字符和字符串以及内存函数的详细介绍与模拟实现(上篇)

文章目录 字符函数字符输入输出函数字符输入函数字符输出函数 字符分类函数字符转换函数 字符串函数字符串输入输出函数字符串输入函数字符串输出函数 strlen函数的使用和模拟实现strcpy函数的使用和模拟实现strcat函数的使用和模拟实现strcmp函数的使用和模拟实现strncpy函数的…

Python基于TensorFlow实现卷积神经网络-双向长短时记忆循环神经网络分类模型(CNN-BiLSTM分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 随着人工智能技术的快速发展&#xff0c;深度学习已经成为处理复杂数据集的关键工具之一。其中&#x…

【Kubernetes】k8s集群资源调度

目录 一、k8s的List-Watch机制 二、scheduler的调度过程 三、指定节点调度Pod 1.通过nodeName调度Pod 2.通过节点标签选择器调度Pod 3.通过亲和性调度Pod 1&#xff09;节点亲和性 2&#xff09;Pod 亲和性 四、污点(Taint) 和 容忍(Tolerations) 1.污点(Taint) 2.…

靶机:DC-2

一、信息收集 1、主机发现 nmap 192.168.236.0/24 2、端口扫描 nmap 192.168.236.130 -p- -A 二、漏洞探测 访问192.168.236.130&#xff0c;URL重定向&#xff0c;在本地hosts文件中添加192.168.236.130 dc-2 在flag1中提示cewl工具&#xff0c;kali自带&#xff0c;把密码…

进阶SpringBoot之 Web 静态资源导入

idea 创建一个 web 项目 新建 controller 包下 Java 类&#xff0c;用来查验地址是否能成功运行 package com.demo.web.controller;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;RestControl…

如何在linux系统上部署nginx

1&#xff09;首先去 nginx.org/download 官网下载你所需要的版本 我这里是下载的 nginx-1-23-3.tar.gz 2&#xff09;然后执行 yum -y install lrzsz 安装文件上传软件 执行 rz 选择你下载nginx的位置进行上传 yum -y install lrzsz 3&#xff09;执行 tar -zxvf nginx-1.23…

《RT-DETR》论文笔记

原文出处 [2304.08069] DETRs Beat YOLOs on Real-time Object Detection (arxiv.org)https://arxiv.org/abs/2304.08069 原文笔记 What DETRs Beat YOLOs on Real-time Object Detection 1、设计了一种高效的混合编码器&#xff0c;通过解耦尺度内交互和跨尺度融合来提高…

保障速度与安全合规的前提下,如何传文件到国外?

伴随着经济全球化&#xff0c;数据跨境活动日益频繁&#xff0c;数据出境场景越来越多&#xff0c;防范数据出境安全风险&#xff0c;保障数据依法有序自由流动成为我国关注的重要方面。涉及数据出海的行业多种多样&#xff0c;像跨国运营、全球研发、金融服务等领域的企业都涉…

音乐制作工具:Studio One 6 (WinMac)

Studio One 6是由PreSonus公司开发的一款专业音乐制作软件&#xff0c;它提供了丰富的功能&#xff0c;以满足音乐创作、录制、混音和母带处理的需求。 Studio One 6以其人性化的用户界面、强大的音频性能、以及丰富的功能&#xff0c;成为了音乐制作领域中一个非常受欢迎的选择…

JAVA练习(五)对象封装

选择题 1、【static使用】 如果有以下程序片段&#xff1a; public class Some{private Some some;private Some(){}public static Some create(){if(some null){some new Some();}return some;} }以下描述哪个正确&#xff1a; A、编译错误 B、客户端必须使用new Some()产…

政务数据共享交换平台的逻辑架构

政务数据共享交换平台基于主流大数据技术和政务数据共享交换规范&#xff0c;提供大数据工作门户、工单系统、资源目录管理平台、数据交换平台和API管理平台&#xff0c;如 政务数据共享交换平台主要包括大数据工作门户、数据资源目录管理平台、共享交换管理平台、API管理平台、…

Python07:循环结构 --> for-in循环

如果循环次数已经确定&#xff0c; for循环 """ eg05 - 循环结构 --> for-in循环Author: mimo_yy Date: 2024/5/15 """ # 如果循环次数已经确定 for循环 for i in range(100): # 产生0到100范围的整数&#xff0c;从0开始取数&#xff0c;1…

【Impala】学习笔记

Impala学习笔记 【一】Impala介绍【1】简介&#xff08;1&#xff09;简介&#xff08;2&#xff09;优点&#xff08;3&#xff09;缺点 【2】架构&#xff08;1&#xff09;Impalad&#xff08;守护进程&#xff09;&#xff08;2&#xff09;Statestore&#xff08;存储状态…

【智能控制】第8章 典型神经网络 ,单神经元网络,BP神经网络,RBF神经网络,Hopfield神经网络(北京航天航空大学)

目录 第8章 典型神经网络 1. 单神经元网络 2. BP神经网络 3. RBF神经网络 4. Hopfield神经网络 第8章 典型神经网络 根据神经网络的连接方式&#xff0c;神经网络可分为三种形式&#xff1a;前馈型神经网络、反馈型神经网络和自组织网络&#xff0c;。典型的前馈型神经…

嵌入式边缘计算软硬件开发实训室解决方案

一、 引言 随着5G通信技术、人工智能算法和大数据分析方法的迅猛发展&#xff0c;物联网(IoT)设备的数量正以前所未有的速度增长&#xff0c;这些设备每天产生着海量的数据。据预测&#xff0c;到2025年&#xff0c;全球将有超过750亿个连接的IoT设备。这些设备不仅包括常见的智…

ArcGIS基础:标注转注记及简单处理

注记是一个静态的标签图层&#xff0c;能够独立的保存为文件&#xff0c;并且具有计算功能&#xff1b; 标注是一个动态的标签图形&#xff0c;无法以文件的形式进行存储和计算&#xff1b; 2者各有优势和劣势&#xff0c;根据具体需求进行选择 需要注意的是注记要存储在GDB…