一:场景说明

       最近接到一个需求,让我解析Excel表。这要是简单常规的Excel表,那我还能摸一摸鱼给他整出来,主要是给我的Excel表长得跟下图中的Excel表一样复杂难搞,这可把我难倒了。于是开启了我的百度之旅,有可能是我不会百度或者理解能力太差,反正就是我的问题。愣是找不到一篇文章能让我这种智力不足的人弄明白该如何解析这种复杂的Excel表格。于是我决定按照自己思维解析这种复杂的Excel表格,希望能得到小伙伴们的认可。

图片来源:https://blog.csdn.net/weixin_42803027/article/details/110189928

在这里插入图片描述

二:Apache POI常用类

​        Java中有很多框架可以解析Excel文件这里使用到POI去解析Excel文件。由于前辈们已经给出了很多很好的使用说明了,这里我就直接粘几篇我看到觉得很好的POI使用详解文章。(Apache POI使用详解_vbirdbest的博客-CSDN博客POI核心类 – POI教程 (yiibai.com)

       这里使用到是XSSF接口,毕竟除了能解析xlsx格式的Excel以外,也可以兼容解析xls格式的Excel。详细的使用可以常考上面的两篇文章,第一篇是讲HSSF的用法,第二篇是讲XSSF的用法。两者的用法非常相似建议篇文章结合使用。为了消除接下来代码阅读障碍,这里简单讲解一下案例使用到的方法

// 创建一个关联输入流的工作簿
Workbook workbook = WorkbookFactory.create(file.getInputStream());
// 根据工作下标获取指定工作
Sheet sheet = workbook.getSheetAt(sheetIndex);
// 获取工作表中的行,此处为第一行
Row row = sheet.getRow(0);
// 获取工作表中第一行的第一列单元格
Cell cell = row.getCell(0);
// 获取单元格属性值,由于cell有相应的单元格类型以及对应的值也有其数据类型,所以获取值的路途可能比较麻烦点。这里简单获取它的值一下,接下来会有一个专门的函数去获取它的值。
cell.getStringCellValue();

// 获取sheet工作表中所有的合并单元格个数
int sheetMergerCount = sheet.getNumMergedRegions();
// 根据下标获取工作表中指定合并单元格
CellRangeAddress cra = sheet.getMergedRegion(i);

Excel中的工作簿、工作表、行、单元格中的关系

一个Excel文件对应一个workbook(XSSFWorkbook),
一个workbook可以多个sheet(XSSFSheet)组成,
一个sheet是由多个row(XSSFRow)组成,
一个row是由多个cell(XSSFCell)组成

为了加深对Excel的了解,这里结合图片详细讲解一下。

在这里插入图片描述

三:案例分析

1、思路说明
  1. 在真正读取复杂Excel表格前,需要解析一下这个Excel表格的模板,也就是这个复杂Excel表格没有输入值的状态如上图所示
  2. 在解析这个Excel表格模板时,我们需要一行一行的遍历每一个单元格判断它的值是否null。为null说明单元格是用来存储数据的,此时我们需要记录下它的位置行号以及列号)。若该单元格为合并单元格时,只需记录第一行、第一列对应单元格的位置即可。若不为null,则说明其只是一个数标识,如:姓名、性别等这类数据标识,并不需要记录相应位置
  3. 这里可能会有些疑问,POI会不会识别黑框表单之外的单元格。需要说明的是,excel表格设计好了是不会识别黑框框外的单元格的。
  4. 重点:由于复杂表格是合并单元格和普通单元格混杂一起的,而遍历单元格的时候,会把合并单元格当成普通单元格去遍历。所以我们需要根据行号和列号去判断当前的单元格是否包含合并单元格中,若是在合并单元格中,需要判断一下是否为该合并单元格的第一行、第一列。若为该合并单元格的第一行、第一列就去读数据,否则根据该合并单元格的最后一列下标去到当前行的下一个单元格。普通单元格直接读取判断其值即可
  5. 解析完Excel模板后,就会得到一组用于数据存储的单元格的位置,这时我们只需根据这些位置上传的Excel表中取值即可
  6. 需要注意的是,我们是从左到右、从上到下的去存储数据存储单元格的位置,所以我们定义数据属性时候也需要根据这个顺序来,否则在通过反射机制自动装配数据类对象属性时会出现顺序错误。(对于自动装配数据类对象属性不熟悉的小伙伴可以参考Java通过反射机制获取数据类对象的属性及方法
2、重要代码展示
1、获取cell单元格中的数据
/**
 * 获取cell单元格中的值
 * @param cell
 * @return
 */
private Object getValue(Cell cell) {
    Object value = null;
    if (cell == null) {
        return null;
    } else if (cell.getCellType().equals(CellType.FORMULA)) {   // 单元格的数据属于公式
        switch (cell.getCachedFormulaResultType()) {
            case STRING:
                // 字符串类型
                value = cell.getStringCellValue();
                break;
            case NUMERIC:
                // double类型
                value = cell.getNumericCellValue();
                break;
            case BOOLEAN:
                // boolean类型
                value = cell.getBooleanCellValue();
                break;
            default:
                // 字符串类型
                value = cell.getCellFormula();
        }
    } else if (cell.getCellType().equals(CellType.NUMERIC)) {   // 单元格的数据属于数字
        if (DateUtil.isCellDateFormatted(cell)) {
            value = cell.getDateCellValue();
        } else {
            value = new BigDecimal(cell.toString());
        }
    } else {
        value = StringUtils.isBlank(cell.toString()) ? null : cell.toString();
    }
    return value;
}
2、通过行号和列号判断当前单元格是否属于合并单元格(MergedRegion类存储的是合并单元格的起始行号结束行号、起始列号、结束列号)
/**
 * 判断cellRow行、cellCol对应的单元格是否为合并单元格
 * @param sheet	工作表
 * @param cellRow	cell对应的行号
 * @param cellCol	cell对应的列号
 * @return
 */
private MergedRegion getMergerCellRegionRow(Sheet sheet, int cellRow, int cellCol) {
    // 获取sheet工作表中所有的合并单元格的个数
    int sheetMergerCount = sheet.getNumMergedRegions();
    for (int i = 0; i < sheetMergerCount; i++) {
        // 获取指定合并单元格
        CellRangeAddress cra = sheet.getMergedRegion(i);
        int firstRow = cra.getFirstRow(); // 合并单元格CELL起始行
        int firstCol = cra.getFirstColumn(); // 合并单元格CELL起始列
        int lastRow = cra.getLastRow(); // 合并单元格CELL结束
        int lastCol = cra.getLastColumn(); // 合并单元格CELL结束
        // 判断cellRow行、cellCol列对应的单元格是否在该合并单元格的范围
        if (cellRow >= firstRow &amp;&amp; cellRow <= lastRow) {
            if (cellCol >= firstCol &amp;&amp; cellCol <= lastCol) {
                return new MergedRegion(firstRow, lastRow, firstCol, lastCol);
            }
        }
    }
    return null;
}
3、解析复杂Excel模板(TargetRegion存储的是数据存储单元格的行号和列号,targetRegions是TargetRegion类的List集合用来存储所有的数据存储单元格的位置)
// 解析复杂excel表,并将目标值的位置存储起来
private void readExcelMixMergedRegion(Sheet sheet) {
    System.out.println("======= 解析Excel文件start =======");
    // 获取工作表中行数
    int numberOfRows = sheet.getPhysicalNumberOfRows();
    for (int i = 0; i < numberOfRows; i++) {
        Row row = sheet.getRow(i);
        // 遍历每行的列(即遍历所有单元格)
        for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
            System.out.printf("(%d-%d)", i, j);
            int rowIndex = i, columnIndex = j;
            // 判断行号为i,列号为j的单元格是否为合并单元格,不为合并单元格返回null,若为合并单元格返回MergedRegion对象
            MergedRegion mergedRegion = getMergerCellRegionRow(sheet, i, j);
            Cell cell = null;
            if (mergedRegion != null) { // 若为合并单元格,需要进行进一步处理
                // 若当前行号和列号是合并单元格的第一行、第一列,就获取单元格(合并单元格只有其第一行、第一列的位置有值)
                if (i == mergedRegion.getFirstRow() &amp;&amp; j == mergedRegion.getFirstColumn()) {
                    cell = row.getCell(j);  // 获取单元格
                    j = mergedRegion.getLastColumn();  // 将列下标移到当前行的下一个单元格列下标,减少不必要的扫描
                }else{  // 若为合并单元格的非第一行,则直接将列下标移到当前行的下一个单元格列下标,并进行接下来的扫描
                    j = mergedRegion.getLastColumn();   //
                    continue;
                }
            } else {    // 若不为合并单元格,直接获取单元格
                cell = row.getCell(j);
            }
            Object val = getValue(cell);    // 获取单元格的值
            if (val==null) {    // 若单元格的值为null,说明该单元格为目标单元格,需要存储它的位置
                TargetRegion targetRegion = new TargetRegion(rowIndex, columnIndex);
                targetRegions.add(targetRegion);
            }
            System.out.println(val==null?null:val.toString());
        }
    }
    System.out.println("======= 解析Excel文件end =======");
}
4、根据数据存储单元格的位置去获取相关数据
/**
 * 根据存储的目标值位置去获取相应的value值
 * @param sheet
 * @param paramsValueList   存储目标容器
 */
private void getTargetCellsValue(Sheet sheet,List<Object> paramsValueList) {
    System.out.println("======= 读取目标start =======");
    targetRegions.forEach(item -> {
        int row = item.getRow();
        int column = item.getColumn();
        System.out.print("("+row+","+column+") ");
        Object val = getValue(sheet.getRow(row).getCell(column));
        paramsValueList.add(val);
        System.out.println(val==null?null:val.toString());
    });
    System.out.println("======= 读取目标end =======");
}
3、成果展示

待解析模板:

在这里插入图片描述

解析成果:
请添加图片描述

四:项目示例代码

项目包含测试文件,以及刘姥姥都说看得懂的代码注解可以放心食用。下载项目配置环境后只需运行测试案例(FileTest)即可

示例代码只需5积分,绝对的良心之作,还在等什么,冲冲冲!

https://download.csdn.net/download/Mr_Hugo/86036793

原文地址:https://blog.csdn.net/Mr_Hugo/article/details/125748253

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_30274.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注