【Java】Apache POI 解析带有合并单元格的Excel

树状结构表格读取,按每一行获取其信息。需要读取的表格如下:

依赖引入

    <!-- 针对2007以上版本的库 -->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>4.1.2</version>
    </dependency>

    <!-- 针对2003版本的库 -->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-scratchpad</artifactId>
      <version>4.1.2</version>
    </dependency>

ReadMergeCellExcelUtil 工具类

import com.riching.cloud.common.exception.BadRequestException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbookFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * 解析带有合并单元格的Excel工具类
 */
public class ReadMergeCellExcelUtil {

  /**
   * Excel读取成对象
   *
   * @param file
   * @return
   */
  public static List<Map<String, String>> readExcelToObj(MultipartFile file) {
    if (file == null) {
      throw new BadRequestException("导入文件不能为空");
    }
    try {
      InputStream inputStream = file.getInputStream();
      Workbook workbook = XSSFWorkbookFactory.create(inputStream);
      return readExcel(workbook, 0, 0, 0);

    } catch (IOException e) {
      throw new BadRequestException("导入文件失败");
    }
  }

  /**
   * 读取Excel内容
   *
   * @param workbook      文件
   * @param sheetIndex    第几个sheet
   * @param startReadLine 开始行
   * @param tailLine      结束行
   * @return
   */
  private static List<Map<String, String>> readExcel(Workbook workbook, int sheetIndex, int startReadLine, int tailLine) {
    Sheet sheet = workbook.getSheetAt(sheetIndex);
    FormulaEvaluator formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
    List<Map<String, String>> result = new ArrayList<>();
    for (int i = startReadLine; i <= sheet.getLastRowNum() - tailLine; i++) {
      Row row = sheet.getRow(i);
      Map<String, String> rowData = parseRow(sheet, row, formulaEvaluator);
      if (rowData != null) {
        result.add(rowData);
      }
    }
    return result;
  }

  /**
   * 解析Excel行
   *
   * @param sheet
   * @param row
   * @param formulaEvaluator
   * @return
   */
  private static Map<String, String> parseRow(Sheet sheet, Row row, FormulaEvaluator formulaEvaluator) {
    if (sheet == null || row == null || formulaEvaluator == null) {
      return null;
    }
    Map<String, String> rowData = new TreeMap<>(Comparator.naturalOrder());
    rowData.put("rowIndex", row.getRowNum() + "");

    int cellIndex = 0;
    for (Cell cell : row) {
      String cellValue = getCellValue(sheet, cell, formulaEvaluator);
      rowData.put("field" + cellIndex, cellValue);
      cellIndex++;
    }
    return rowData;
  }

  /**
   * 获取单元格值
   *
   * @param sheet
   * @param cell
   * @param formulaEvaluator
   * @return
   */
  private static String getCellValue(Sheet sheet, Cell cell, FormulaEvaluator formulaEvaluator) {
    if (sheet == null || cell == null || formulaEvaluator == null) {
      return "";
    }
    if (isMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex())) {
      // 获取合并单元格的值
      return getMergedRegionValue(sheet, cell.getRowIndex(), cell.getColumnIndex(), formulaEvaluator);
    } else {
      // 获取普通单元格的值
      return getCellStringValue(cell, formulaEvaluator);
    }
  }

  /**
   * 判断单元格是不是合并单元格
   *
   * @param sheet
   * @param row
   * @param column
   * @return
   */
  private static boolean isMergedRegion(Sheet sheet, int row, int column) {
    if (sheet != null) {
      for (CellRangeAddress range : sheet.getMergedRegions()) {
        if (range.isInRange(row, column)) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * 获取合并单元格值
   *
   * @param sheet
   * @param row
   * @param column
   * @param formulaEvaluator
   * @return
   */
  private static String getMergedRegionValue(Sheet sheet, int row, int column, FormulaEvaluator formulaEvaluator) {
    if (sheet == null || formulaEvaluator == null) {
      return "";
    }
    for (CellRangeAddress range : sheet.getMergedRegions()) {
      if (range.isInRange(row, column)) {
        Row firstRow = sheet.getRow(range.getFirstRow());
        Cell firstCell = firstRow.getCell(range.getFirstColumn());
        return getCellStringValue(firstCell, formulaEvaluator);
      }
    }
    return "";
  }

  /**
   * 获取单元格信息
   *
   * @param cell
   * @param formulaEvaluator
   * @return
   */
  private static String getCellStringValue(Cell cell, FormulaEvaluator formulaEvaluator) {
    if (cell == null || formulaEvaluator == null) {
      return "";
    }
    switch (cell.getCellType()) {
      case STRING:
        return cell.getStringCellValue();
      case BOOLEAN:
        return String.valueOf(cell.getBooleanCellValue());
      case FORMULA:
        try {
          return cell.getCellFormula();
        } catch (Exception var2) {
          return String.valueOf(cell.getNumericCellValue());
        }
        // return formulaEvaluator.evaluate(cell).formatAsString();
      case NUMERIC:
        return String.valueOf(cell.getNumericCellValue());
      default:
        return "";
    }
  }

}

本博客内容根据个人理解所写,有什么错误和误解希望能反映给博主,感谢。

<think>嗯,用户想知道如何使用Apache POI获取Excel合并单元的行列信息。我需要先回忆一下相关的知识。记得Apache POI处理Excel的时候,合并单元的信息是通过Sheet接口的getMergedRegions()方法获取的。这个方法返回一个列表,里面包含所有的CellRangeAddress对象,每个对象代表一个合并的区域。 首先,我应该确认用户的问题点。用户可能已经知道如何读取普通单元,但合并单元的结构比较复杂,因为它们跨越多个行和列。用户需要知道如何遍历这些合并区域,并获取它们的起始和结束行列索引。 接下来,我需要参考提供的引用内容。比如引用[2]提到了合并单元的方法,使用addMergedRegion并传入CellRangeAddress。这可能意味着读取的时候也是通过类似的方式获取。引用[3]提到不能用row.getPhysicalNumberOfCells(),可能会有空cell,所以在处理合并单元时需要注意空值的问题。 然后,我应该分步骤说明如何实现。首先,获取Sheet对象,然后调用getMergedRegions()得到所有合并区域。每个CellRangeAddress对象有getFirstRow()、getLastRow()、getFirstColumn()、getLastColumn()方法,可以获取合并区域的起始和结束的行列索引。 可能需要提醒用户注意,合并后的单元内容只存在于第一个单元中,其他单元可能为空或重复,所以在读取数据时,需要判断单元是否属于合并区域,并处理主单元的值。此外,遍历所有合并区域,并存储这些信息,可能有助于后续的数据处理。 用户的问题还可能涉及到如何判断某个单元是否属于合并区域。这时候可以通过遍历所有合并区域,检查该单元的行列索引是否在某个CellRangeAddress的范围内。或者,使用Sheet的getMergedRegion(int index)方法来逐个检查。 另外,需要注意处理可能出现的异常情况,比如空单元或者索引溢出的问题,引用[3]提到不能使用row.getPhysicalNumberOfCells(),所以可能需要其他方法,如row.getLastCellNum(),但需要处理可能的空值。 最后,可能需要给出一个示例代码,展示如何遍历合并区域并打印出它们的行列信息。同时,提醒用户在读取单元数据时,要检查是否为合并区域的主单元,避免重复或空值的问题。 总结一下,步骤大概是: 1. 获取Sheet对象。 2. 使用sheet.getMergedRegions()获取所有合并区域。 3. 遍历每个CellRangeAddress,获取其起始和结束的行列索引。 4. 在读取单元数据时,判断该单元是否属于某个合并区域,如果是,则可能需要处理主单元的值。 需要确保代码示例正确,比如使用JavaApache POI库,正确处理合并单元的信息。可能还需要提到如何判断某个单元是否是合并单元的一部分,以及如何获取其主单元的值。</think>### 如何读取 Excel 合并单元的行列信息 #### 一、获取合并单元索引的核心方法 使用 Apache POI 读取合并单元的行列信息,需要通过 `Sheet.getMergedRegions()` 方法获取所有合并区域,再通过 `CellRangeAddress` 对象解析具体范围。以下是具体步骤: 1. **获取合并区域集合** ```java List<CellRangeAddress> mergedRegions = sheet.getMergedRegions(); ``` 2. **解析合并范围** 每个 `CellRangeAddress` 对象包含以下关键方法: - `getFirstRow()`:起始行索引(从0开始) - `getLastRow()`:结束行索引 - `getFirstColumn()`:起始列索引(从0开始) - `getLastColumn()`:结束列索引 #### 二、完整代码示例 ```java import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; public class ReadMergedCells { public static void main(String[] args) throws Exception { Workbook workbook = WorkbookFactory.create(new File("input.xlsx")); Sheet sheet = workbook.getSheetAt(0); // 遍历所有合并区域 for (int i = 0; i < sheet.getNumMergedRegions(); i++) { CellRangeAddress mergedRegion = sheet.getMergedRegion(i); System.out.println("合并区域 " + (i+1) + ":"); System.out.println("起始行: " + mergedRegion.getFirstRow()); System.out.println("结束行: " + mergedRegion.getLastRow()); System.out.println("起始列: " + mergedRegion.getFirstColumn()); System.out.println("结束列: " + mergedRegion.getLastColumn()); System.out.println("-----------------------"); } } } ``` #### 三、关键注意事项 1. **数据读取策略**:合并区域中只有第一个单元(左上角)包含有效数据,其他单元需通过起始行列索引获取数据[^2] 2. **空单元处理**:使用 `row.getCell(j)` 时需判空,避免空指针异常[^3] 3. **索引验证**:建议通过 `mergedRegion.isInRange(rowIndex, columnIndex)` 判断单元是否在合并区域内 #### 四、典型应用场景 当需要处理以下情况时,必须获取合并区域信息: - 生成带合并式的 Excel 报表 - 解析复杂表结构(如财务报表) - 数据清洗时处理重复值
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值