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

EasyExcel官方文档

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

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

可以看出,存在几个地方合并单元的数据,如果什么都不处理,直接读取当前数据,结果如下:

很明显这并不是我想要的,因为按照excel合并单元格之后,例如张三和李四的年龄,都应该是20才对,但实际的数据只有张三的年龄是20,李四的年龄是null。

我在网上查询的时候,看到有人说,easyexcel只有合并单元格的左上角单元格才有值,其他的单元格没有值。

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

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

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

具体实现如下:

java 复制代码
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);
    }
}
java 复制代码
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<>();

		// 从excel文件的第几行开始读取,默认是从第二行开始的(认为第一行是标题行)
        final int headRowNumber = 0;

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

		// 循环所有sheet页,读取数据,都放入datas集合中
        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;

    }

}
java 复制代码
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,你就明白了。

相关推荐
慧都小妮子2 小时前
标签打印如何减少手工抄数?用Bartender 直连 Excel 批量打印
excel·bartender·条码打印·标签打印软件·数据驱动打印·工业打印机
SunnyDays101110 小时前
Java 设置 Excel 数据验证:整数、日期、文本长度、下拉列表和时间
java·excel·数据验证·下拉列表
Metaphor69211 小时前
使用 Python 设置 Excel 行列自适应 【代码示例】
python·excel
用户0332126663671 天前
使用 Python 设置 Excel 行列自适应 【代码示例】
python·excel
企业数字化笔记1 天前
固定资产Excel批量导入怎么防重复?资产编码、重复检查和错误回执
java·后端·excel
CORNERSTONE3651 天前
低代码开发有哪些应用场景?
低代码·excel·erp·mes·企业数字化
小葱炖豆腐2 天前
python绘制excel折线图
python·excel·numpy·pandas·matplotlib
cspttty2 天前
2026年财务分析岗JD中的Excel、SQL、BI与业务分析要求
大数据·sql·excel
cspttty2 天前
HR数字化校招准备路线:Excel、SQL、BI和AI工具怎么学
人工智能·sql·excel
鲲穹AI种草3 天前
Excel 表格批量处理与数据整理,多款表格工具能力客观记录
excel·表格处理