使用 Apache POI 实现 Excel 单元格合并

news2025/3/10 11:02:17

在日常工作中,Excel 是一个不可或缺的工具,尤其是在处理大量数据时。为了提升数据的可读性和美观性,我们经常需要对 Excel 中的单元格进行合并操作。本文将介绍如何使用 Apache POI 库在 Java 中实现 Excel 单元格的合并,并提供一个现成的工具类供大家使用。

工具类介绍

我们提供了一个名为 ExcelMergeUtility 的工具类,它可以帮助你轻松地合并 Excel 中的单元格。该类支持纵向合并(按行合并)和横向合并(按列合并),并且可以指定需要合并的列。

工具类代码

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.stereotype.Component;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

@Component
public class ExcelMergeUtility {

    /**
     * 合并Excel中内容相同的单元格
     *
     * @param workbook       工作簿
     * @param sheetName      工作表名称
     * @param columnsToMerge 需要合并的列索引列表
     * @param isRowMerge     是否按行合并(纵向合并)
     * @param isColumnMerge  是否按列合并(横向合并)
     */
    public void mergeCells(Workbook workbook, String sheetName, List<Integer> columnsToMerge,
                           boolean isRowMerge, boolean isColumnMerge) {
        Sheet sheet = workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new IllegalArgumentException("工作表 " + sheetName + " 不存在");
        }

        // 记录第一列的合并行数
        int firstColMergeEndRow = 0;

        for (int colIndex : columnsToMerge) {
            for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
                Row currentRow = sheet.getRow(rowIndex);
                if (currentRow == null) continue;

                Cell currentCell = currentRow.getCell(colIndex);
                if (currentCell == null) continue;

                String currentValue = currentCell.getStringCellValue();

                if (isRowMerge) {
                    // 纵向合并
                    int mergeStartRow = rowIndex;
                    while (rowIndex + 1 <= sheet.getLastRowNum()) {
                        Row nextRow = sheet.getRow(rowIndex + 1);
                        if (nextRow == null) break;

                        Cell nextCell = nextRow.getCell(colIndex);
                        if (nextCell == null || !nextCell.getStringCellValue().equals(currentValue)) break;

                        // 如果当前列不是第一列,且合并行数超过前一列的合并行数,则停止合并
                        if (colIndex > 0 && rowIndex + 1 > getMergeEndRow(sheet, mergeStartRow, colIndex - 1)) break;

                        // 如果合并行数超过第一列的合并行数,则停止合并
                        if (colIndex > 0 && rowIndex + 1 > firstColMergeEndRow) break;

                        rowIndex++;
                    }
                    if (mergeStartRow != rowIndex) {
                        sheet.addMergedRegion(new CellRangeAddress(mergeStartRow, rowIndex, colIndex, colIndex));
                    }

                    // 如果是第一列,记录合并的最后一行
                    if (colIndex == 0) {
                        firstColMergeEndRow = rowIndex;
                    }
                }

                if (isColumnMerge) {
                    // 横向合并
                    int mergeStartCol = colIndex;
                    while (colIndex + 1 < currentRow.getLastCellNum()) {
                        Cell nextCell = currentRow.getCell(colIndex + 1);
                        if (nextCell == null || !nextCell.getStringCellValue().equals(currentValue)) break;

                        colIndex++;
                    }
                    if (mergeStartCol != colIndex) {
                        sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, mergeStartCol, colIndex));
                    }
                }
            }
        }
    }

    /**
     * 获取指定单元格的合并的最后一行
     *
     * @param sheet     工作表
     * @param rowIndex  行索引
     * @param colIndex  列索引
     * @return 合并的最后一行
     */
      private int getMergeEndRow(Sheet sheet, int rowIndex, int colIndex) {
        int numMergedRegions = sheet.getNumMergedRegions();
        for (int i = 0; i < numMergedRegions; i++) {
            CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
            if (mergedRegion.isInRange(rowIndex, colIndex)) {
                return mergedRegion.getLastRow();
            }
        }
//        for (CellRangeAddress mergedRegion : sheet.getMergedRegions()) {
//            if (mergedRegion.isInRange(rowIndex, colIndex)) {
//                return mergedRegion.getLastRow();
//            }
//        }
        return rowIndex; // 如果没有合并,则返回当前行
    }

    /**
     * 示例:生成Excel并合并单元格
     */
    public void generateAndMergeExcel(String filePath, String sheetName, List<Integer> columnsToMerge,
                                      boolean isRowMerge, boolean isColumnMerge) throws IOException {
        // 打开现有的Excel文件
        Workbook workbook = WorkbookFactory.create(new FileInputStream(filePath));

        // 合并单元格
        mergeCells(workbook, sheetName, columnsToMerge, isRowMerge, isColumnMerge);

        // 写入文件
        try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
            workbook.write(fileOut);
        }

        workbook.close();
    }
}

调用示例

List<Integer> columnsToMerge = Arrays.asList(0, 1, 2, 3, 4, 5); // 合并第1列和第2列
boolean isRowMerge = true; // 启用纵向合并
boolean isColumnMerge = false; // 禁用横向合并

excelMergeUtility.generateAndMergeExcel("C:\\Users\\xxxx\\Downloads\\汇总记录2025-03-04%2B10_38_30.xls", "汇总", columnsToMerge, isRowMerge, isColumnMerge);

依赖配置

为了使用这个工具类,你需要在你的项目中添加 Apache POI 的依赖:

<!-- Apache POI 核心库 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<!-- Apache POI OOXML 库,用于处理 .xlsx 文件 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>

在这里插入图片描述
在这里插入图片描述

总结

通过 ExcelMergeUtility 工具类,你可以轻松地实现 Excel 单元格的合并操作。无论是纵向合并还是横向合并,该工具类都能满足你的需求。希望本文对你有所帮助,欢迎在评论区分享你的使用体验和问题。

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

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

相关文章

低代码开发直聘管理系统

低代码 DeepSeek 组合的方式开发直聘管理系统&#xff0c;兼职是开挂的存在。整个管理后台系统 小程序端接口的输出&#xff0c;只花了两个星期不到。 一、技术栈 后端&#xff1a;SpringBoot mybatis MySQL Redis 前端&#xff1a;Vue elementui 二、整体效果 三、表结…

Android更新时区版本-ianaVersion

代码路径 源码路径system/timezone 目录结构&#xff1a; apex 包含与Android时区更新APEX文件相关的代码&#xff0c;用于在Android设备上更新时区规则。 debug_tools 包含调试Android设备时区问题的工具。 distro 包含与“分发包”&#xff08;distros&#xff09;相关…

利用python实现对Excel文件中数据元组的自定义排序

问题引入&#xff1a; 假设你是一个浙江省水果超市的老板&#xff0c;统筹11个下辖地市的水果产量。假设11个地市生产的水果包括&#xff1a;苹果、香蕉和西瓜。你如何快速得到某种水果产量突出&#xff08;排名前几&#xff09;的地市&#xff1f;产量落后&#xff08;排名后…

leetcode 0018 四数之和-medium

1 题目&#xff1a;四数之和 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重复&#x…

时序数据库 InfluxDB 3.0 版本性能实测报告:写入吞吐量提升效果验证

亮点总结&#xff1a; TSBS 测试表明&#xff0c;对于少于 100 万台设备的数据集&#xff0c;InfluxDB OSS 3.0 的数据写入速度实际上比 InfluxDB OSS 1.8 更慢。 对于 100 万台及以上设备的数据集&#xff0c;InfluxDB OSS 3.0 的数据写入性能才开始超过 InfluxDB OSS 1.8。…

Pytest自动化框架

目录 Pytest简单介绍 第一章&#xff1a;Pytest console命令 1.pytest -v 参数 -h 参数 其他一些参数&#xff1a;仅供参考 第二章. mark标记 pytest.mark.skip pytest.mark.skipif pytest.mark.xfail ​编辑 pytest.mark.patametrize 单个参数 多个参数 直接…

ubuntu22.04安装RAGFlow配合DeepSeek搭建本地知识库

一、简介 RAGFlow 是一个基于对文档的深入理解的开源 RAG&#xff08;检索增强生成&#xff09;引擎。当与 LLM 集成时&#xff0c;它能够提供真实的问答功能&#xff0c;并以来自各种复杂格式数据的有根据的引用为后盾。 二、安装 1.环境要求 CPU ≥ 4 核 &#xff08;x86…

【银河麒麟高级服务器操作系统实例】虚拟机桥接网络问题分析及处理

更多银河麒麟操作系统产品及技术讨论&#xff0c;欢迎加入银河麒麟操作系统官方论坛 https://forum.kylinos.cn 了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer…

springboot011基于springboot的课程作业管理系统(源码+包运行+LW+技术指导)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得难了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等&#xff0c;你想解决的问题&#xff0c;今天…

Android 屏幕适配 Tips

概念 屏幕尺寸&#xff1a;屏幕的对角线的长度屏幕分辨率&#xff1a;屏幕分辨率是指在横纵向上的像素点数&#xff0c;单位是px&#xff0c;1px1个像素点。一般以纵向像素x横向像素&#xff0c;如1960x1080屏幕像素密度&#xff1a;每英寸上的像素点数&#xff0c;单位是dpi …

使用 Arduino 的 WiFi 控制机器人

使用 Arduino 的 WiFi 控制机器人 这次我们将使用 Arduino 和 Blynk 应用程序制作一个 Wi-Fi 控制的机器人。这款基于 Arduino 的机器人可以使用任何支持 Wi-Fi 的 Android 智能手机进行无线控制。 为了演示 Wi-Fi 控制机器人,我们使用了一个名为“Blynk”的 Android 移动应…

使用 Deepseek + kimi 快速生成PPT

前言 最近看到好多文章和视频都在说&#xff0c;使用 Deepseek 和 kimi 能快速生成精美的 ppt&#xff0c;毕竟那都是别人说的&#xff0c;只有自己尝试一次才知道结果。 具体操作 第一步&#xff1a;访问 deepseek 我们访问 deepseek &#xff0c;把我们想要输入的内容告诉…

XHR请求解密:抓取动态生成数据的方法

在如今动态页面大行其道的时代&#xff0c;传统的静态页面爬虫已无法满足数据采集需求。尤其是在目标网站通过XHR&#xff08;XMLHttpRequest&#xff09;动态加载数据的情况下&#xff0c;如何精准解密XHR请求、捕获动态生成的数据成为关键技术难题。本文将深入剖析XHR请求解密…

C#程序加密与解密Demo程序示例

目录 一、加密程序功能介绍 1、加密用途 2、功能 3、程序说明 4、加密过程 5、授权的注册文件保存方式 二、加密程序使用步骤 1、步骤一 ​编辑2、步骤二 3、步骤三 4、步骤四 三、核心代码说明 1、获取电脑CPU 信息 2、获取硬盘卷标号 3、机器码生成 3、 生成…

MC9S12单片机的内存映射机制

地址空间 这是个16位的单片机。CPU的寻址空间最大为2^1664K。 这个64K是包括外设、RAM、EEPROM、和FLASH的。现在程序越来越大&#xff0c;64K的空间肯定是不够用的。因此&#xff0c;需要扩展。 扩展方法就是&#xff1a;分页。 把原来的64K空间&#xff0c;划分一块出来&a…

计算机毕业设计SpringBoot+Vue.js科研项目验收管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Docker Compose企业示例

利用容器编排完成haproxy和nginx负载均衡架构实施 1.mkdir docker.test 2.touch haproxy.yml 3.mkdir /var/lib/docker/volumes/conf 4.dnf install haproxy -y --downloadonly --downloaddir/xixi&#xff1a;下载内容到/xixi目录下 5. rpm2cpio haproxy-2.4.22-4.el9.x8…

【Linux网络#11】: 传输层协议 TCP

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 生活总是不会一帆风顺&#x…

19. 大数据-技术生态简介

文章目录 前言一、Hadoop介绍1. 简介2. Hadoop发展史3. Hadoop现状 二、Hadoop特性1. Hadoop国外应用2. Hadoop国内应用 三、Hadoop架构变迁1. 发行版本2. Hadoop架构变迁(1.0-2.0变迁)3. Hadoop架构变迁(3.0新版本)4. 综述 四、技术生态体系 前言 大数据&#xff08;Big Data…

Android Native 之 文件系统挂载

一、文件系统挂载流程概述 二、文件系统挂载流程细节 1、Init启动阶段 众所周知&#xff0c;init进程为android系统的第一个进程&#xff0c;也是native世界的开端&#xff0c;要想让整个android世界能够稳定的运行&#xff0c;文件系统的创建和初始化是必不可少的&#xff…