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

需求

将带有“合并单元格”的 Excel 进行解析,Excel 示例如下:

方案

Java 生态中 Excel 解析库比较多,下面介绍两种比较常用的库

  1. Apache POI:POI 是用于读写 Microsoft Office 格式文件(如 Excel、Word 和 PowerPoint)的 Java 库。它提供了一组丰富的功能和 API,可用于读取和写入 Excel 文件的各种格式,如 XLS、XLSX 和 CSV 等。
  2. EasyExcel是阿里巴巴开源的一个基于 Java 的简单、省内存的读写 Excel 的开源项目。EasyExcel 在尽可能节约内存的情况下支持读写百 M 级的 Excel 。EasyExcel 能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。EasyExcel 采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。

在本示例中,将使用 POI 来解析 Excel 。需要注意的是 ,POI 对 xlsxlsx 格式的 Excel 采用不同的 API ,分别是 org.apache.poi.hssf.usermodel.HSSFWorkbookorg.apache.poi.xssf.usermodel.XSSFWorkbook

依赖引入

        <dependency>
            <groupId>org.apache.poigroupId>
            <artifactId>poiartifactId>
            <version>4.1.0version>
        dependency>
        <dependency>
            <groupId>org.apache.poigroupId>
            <artifactId>poi-ooxmlartifactId>
            <version>4.1.0version>
        dependency>

代码示例

以下示例展示了解析 xls 格式的 Excel 。

package com.xzbd.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;

import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;

import cn.hutool.core.collection.CollectionUtil;

public class ExcelUtil {
    public static void main(String[] args) throws IOException {

        String filePath = "\\ParseDemo.xls";
        File file = new File(filePath);
        if (!file.exists()) {
            System.out.println(" File not exists ...");
            return;
        }

        FileInputStream fis = new FileInputStream(file);
        Workbook workbook = new HSSFWorkbook(fis);
        Sheet sheet = workbook.getSheetAt(0);
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
        // 遍历每行数据
        for (Row row : sheet) {
            // 遍历每个单元格
            for (Cell cell : row) {
                CellRangeAddress cellRangeAddress = getCellRangeAddress(mergedRegions, cell);
                String value = "";
                if (Objects.nonNull(cellRangeAddress)) {
                    value = getCellRangeAddressValue(cellRangeAddress, sheet);
                } else {
                    value = getStringValue(cell);
                }
                System.out.print(value + "\t");
            }
            System.out.println();
        }
        // 关闭资源
        workbook.close();
        fis.close();
    }

    public static String getCellRangeAddressValue(CellRangeAddress cellRangeAddress, Sheet sheet) {
        if (Objects.isNull(cellRangeAddress)) {
            return StringUtils.EMPTY;
        }
        if (Objects.isNull(sheet)) {
            return StringUtils.EMPTY;
        }
        Row row = sheet.getRow(cellRangeAddress.getFirstRow());
        if (Objects.isNull(row)) {
            return StringUtils.EMPTY;
        }
        Cell cell = row.getCell(cellRangeAddress.getFirstColumn());
        if (Objects.isNull(cell)) {
            return StringUtils.EMPTY;
        }
        return getStringValue(cell);
    }

    public static CellRangeAddress getCellRangeAddress(List<CellRangeAddress> mergedRegions, Cell cell) {
        if (CollectionUtil.isEmpty(mergedRegions)) {
            return null;
        }
        if (Objects.isNull(cell)) {
            return null;
        }

        for (CellRangeAddress mergedRegion : mergedRegions) {

            Boolean in = mergedRegion.isInRange(cell);
            if (in) {
                return mergedRegion;
            }
        }

        return null;
    }

    /**
     * 判断当前单元格是否在合并单元格区域内
     * 
     * @param mergedRegion
     * @param cell
     * @return
     */
    public static Boolean isInMergedRegin(CellRangeAddress mergedRegion, Cell cell) {
        if (Objects.isNull(mergedRegion)) {
            return false;
        }
        if (Objects.isNull(cell)) {
            return false;
        }
        int row = cell.getRowIndex();
        int column = cell.getColumnIndex();
        int firstColumn = mergedRegion.getFirstColumn();
        int lastColumn = mergedRegion.getLastColumn();
        int firstRow = mergedRegion.getFirstRow();
        int lastRow = mergedRegion.getLastRow();
        // 判断当前单元格是否在合并单元格区域内
        if ((row >= firstRow && row <= lastRow) && (column >= firstColumn && column <= lastColumn)) {
            return true;
        }

        return false;
    }

    public static String getStringValue(Cell cell) {
        String value = "";
        // 判断单元格类型
        if (cell.getCellType() == CellType.STRING) {
            value = cell.getStringCellValue();
        } else if (cell.getCellType() == CellType.BOOLEAN) {
            value = String.valueOf(cell.getBooleanCellValue());
        } else if (cell.getCellType() == CellType.NUMERIC) {
            value = String.valueOf(cell.getNumericCellValue());
        }
        return value;
    }
}


运行结果

以下是解析 Excel ,打印出的结果

xxx相关信息     xxx相关信息     xxx相关信息
大类    小类    材料
水果类  苹果    花牛苹果
水果类  香蕉    老山香蕉
水果类  梨      巴山雪梨
蔬菜类  蒜苗    红皮蒜苗
蔬菜类  蒜苗    白皮蒜苗
蔬菜类  辣椒    小米椒
蔬菜类  茄子    胖茄子
肉类    鱼类    三文鱼
肉类    鱼类    金枪鱼
肉类    鱼类    耗儿鱼

总结

本文基于 xls 格式的 Excel 给出了解析合并单元格的示例。且经过运行打印出了解析结果。

要将工具类改为支持 xlsx 格式的 Excel 解析,只需要如下两步操作:

  • HSSFWorkbook 改为 XSSFWorkbook 并导入包 org.apache.poi.xssf.usermodel.XSSFWorkbook
  • String filePath = "\\ParseDemo.xls"; 改为 String filePath = "\\ParseDemo.xlsx"; 注意 ParseDemo.xlsx 不是仅仅更改名称,还要创建正确的文件。

你可能感兴趣的:(java技术,java,excel,POI)