Java使用POI操作Excel合并单元格

article/2025/11/2 20:40:05

友情链接:Spring Data JPA 动态查询 普通查询

友情链接:利用POI实现动态复杂多级表头

前言   

合并单元格语法: 开始行、结束行、开始列、结束列

对应代码:new CellRangeAddress(startRowIndex, rowIndex - 1, i, i);

合并代码:sxssfSheet.addMergedRegion(cellRangeAddress); 

由此可见:我们只需要知道这样四个参数就行,在工作中,有定制化的合并单元格,已知合并规则,那么在代码中直接写死,还有一种是动态的实现合并单元格。

本文是动态实现合并单元格 。适用于特定场景下合并规则。

1、引入poi依赖

<!--poi-->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.0</version>
</dependency>

2、合并单元格两种方案:

1)数据有层级结构:

     比如部门和部门下的员工、订单信息和订单明细,典型一对多,返回数据结构有层级关系,目前只考虑两层结构。

        表头文案label、表头列对应的属性字段field、列的宽度witdh、列是否有需要脱敏等等可以配置为json到数据库,作为一个job任务,可以适用大部分导出,任务中心就很好的复用导出逻辑。

package com.example.demo.excel;import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;public class Test03ExcelDemo {@SuppressWarnings("unchecked")public static HSSFWorkbook warpSingleWorkbook2(String title, List<Map<String, Object>> mapsList, List<String> head) throws Exception {String[] str = {"id", "name", "num"};String testUsers = "testUsers";String[] str2 = {"userId", "userName", "email"};if (mapsList == null || mapsList.isEmpty()) {throw new NullPointerException("the row list is null");}// 如果要设置背景色 最好用 XSSFWorkbookHSSFWorkbook book = new HSSFWorkbook();HSSFSheet sheet = book.createSheet(title);sheet.setDefaultColumnWidth(20);HSSFCellStyle style = book.createCellStyle();// 生成表头HSSFRow headRow = sheet.createRow(0);for (int i = 0; i < head.size(); i++) {HSSFCellStyle headStyle = book.createCellStyle();setExcelValue(headRow.createCell(i), head.get(i), headStyle);}int rowIndex = 1;int commonTotalSize = mapsList.get(0).size() - 1;List<List<Integer>> mergeParams = new ArrayList<>();for (Map<String, Object> map : mapsList) {// 记录合并的开始行int startRowIndex = rowIndex;HSSFRow bodyRow = sheet.createRow(rowIndex++);for (int i = 0; i < str.length; i++) {setExcelValue(bodyRow.createCell(i), map.get(str[i]), style);}//组装数据的时候至少又一个,没有数据空串填充一个数据List<Map<String, Object>> list = (List<Map<String, Object>>) map.get(testUsers);for (int i = 0; i < str2.length; i++) {setExcelValue(bodyRow.createCell(str.length + i), null, style);}for (int i = 1; i < list.size(); i++) {HSSFRow bodyRow2 = sheet.createRow(rowIndex++);for (int j = 0; j < str2.length; j++) {setExcelValue(bodyRow2.createCell(str.length + j), list.get(i).get(str2[j]), style);}}if (list.size() > 1) {// 依次放入  起始行 结束行 起始列 结束列for (int i = 0; i < commonTotalSize; i++) {List<Integer> mergeParam = new ArrayList<>(4);mergeParam.add(startRowIndex);mergeParam.add(rowIndex - 1);mergeParam.add(i);mergeParam.add(i);mergeParams.add(mergeParam);}}}for (List<Integer> list : mergeParams) {sheet.addMergedRegion(new CellRangeAddress(list.get(0), list.get(1), list.get(2), list.get(3)));}return book;}/*** 设置Excel浮点数可做金额等数据统计** @param cell  单元格类* @param value 传入的值*/public static void setExcelValue(HSSFCell cell, Object value, HSSFCellStyle style) {// 写数据if (value == null) {cell.setCellValue("");} else {if (value instanceof Integer || value instanceof Long) {cell.setCellValue(Long.parseLong(value.toString()));} else if (value instanceof BigDecimal) {cell.setCellValue(((BigDecimal) value).setScale(1, RoundingMode.HALF_UP).doubleValue());} else {cell.setCellValue(value.toString());}cell.setCellStyle(style);}}public static void main(String[] args) {FileOutputStream fileOut = null;try {List<String> head = Arrays.asList("部门ID", "部门", "renshu", "人员ID", "姓名", "邮箱");List<Map<String, Object>> depts = getData();HSSFWorkbook wb = warpSingleWorkbook2("测试", depts, head);File file = new File("/Users/tangshanyuan/test/new2.xls");fileOut = new FileOutputStream(file);wb.write(fileOut);System.out.println("----Excle文件已生成------");} catch (Exception e) {e.printStackTrace();} finally {if (fileOut != null) {try {fileOut.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 模拟查询获取数据** @return*/private static List<Map<String, Object>> getData() {List<Map<String, Object>> depts = new ArrayList<>();for (int i = 0; i < 10; i++) {Map<String, Object> deptMap = new HashMap<>();deptMap.put("id", i + "主键");if (i > 0) {deptMap.put("name", i + "部门");} else {deptMap.put("name", null);}deptMap.put("num", i + "");List<Map<String, String>> testUserList = new ArrayList<>();for (int j = 0; j < new Random().nextInt(10) + 1; j++) {Map<String, String> testUser = new HashMap<>();testUser.put("userId", j + "");testUser.put("userName", j + "姓名");testUser.put("email", j + "544416131");testUserList.add(testUser);}deptMap.put("testUsers", testUserList);depts.add(deptMap);}return depts;}}

示例:明细第一行没有值,是因为代码写死null,注意对应取值

2)数据没有层级结构

      返回是一条条数据,但是数据与数据之间不规则,存在相同的则需要合并,不同纬度的查询返回的数据结构是不同的,比如以商品为度去查询订单,则看到的是所有购买的商品信息

package com.example.demo.excel;import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import org.apache.poi.ss.usermodel.Cell;
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 org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;public class Test02 {/*** @param field* @param title      标题集合 tilte的长度应该与list中的model的属性个数一致* @param listList   内容集合* @param mergeIndex 合并单元格的列*/@SneakyThrowspublic static String createExcel(String[] field, String[] title, List<List<Map<String, String>>> listList, int[] mergeIndex) {long startTime = System.currentTimeMillis();if (title.length == 0) {return null;}// 初始化excel模板Workbook workbook = new XSSFWorkbook();Sheet sheet = null;int n = 0;//循环sheet页 实例化sheet对象并且设置sheet名称,book对象try {sheet = workbook.createSheet();workbook.setSheetName(n, "sheet1");workbook.setSelectedTab(0);} catch (Exception e) {e.printStackTrace();}// 数据总数int size = listList.stream().mapToInt(List::size).sum();assert sheet != null;Row row0 = sheet.createRow(0);for (int i = 0; i < title.length; i++) {Cell cell_1 = row0.createCell(i);cell_1.setCellValue(title[i]);}List<PoiModel> poiModels = Lists.newArrayList();for (List<Map<String, String>> list : listList) {for (Map<String, String> map : list) {int index = sheet.getLastRowNum() + 1;Row row = sheet.createRow(index);for (int i = 0; i < title.length; i++) {String titleField = field[i];String old = null;if (index > 1) {old = poiModels.get(i) == null ? null : poiModels.get(i).getContent();}for (int k : mergeIndex) {if (index == 1) {PoiModel poiModel = PoiModel.builder().oldContent(map.get(titleField)).content(map.get(titleField)).rowIndex(1).cellIndex(i).build();poiModels.add(poiModel);break;}PoiModel poiModel = poiModels.get(i);String content = map.get(titleField);// 当前行的当前列与上一行的当前列的内容不一致时,则把当前行以上的合并if (i > 0 && k == i) {// 如果不需要考虑当前行与上一行内容相同,但是它们的前一列内容不一样则不合并的情况,把或条件删除if (!poiModel.getContent().equals(content) || poiModel.getContent().equals(content) && !poiModels.get(i - 1).getOldContent().equals(map.get(field[i - 1]))) {get(poiModel, content, index, i, sheet);}}// 处理第一列的情况if (k == i && i == 0 && !poiModel.getContent().equals(content)) {get(poiModel, content, index, i, sheet);}// 最后一行没有后续的行与之比较,所有当到最后一行时则直接合并对应列的相同内容if (k == i && index == size && poiModels.get(i).getRowIndex() != index) {CellRangeAddress cra = new CellRangeAddress(poiModels.get(i).getRowIndex(), index, poiModels.get(i).getCellIndex(), poiModels.get(i).getCellIndex());sheet.addMergedRegion(cra);}}Cell cell = row.createCell(i);cell.setCellValue(map.get(titleField));poiModels.get(i).setOldContent(old);}}}File file = new File("/Users/tangshanyuan/test/demo.xls");FileOutputStream fout = new FileOutputStream(file);workbook.write(fout);fout.close();long endTime = System.currentTimeMillis();    //获取结束时间System.out.println("程序运行时间:" + (endTime - startTime) + "ms");    //输出程序运行时间return file.getAbsolutePath();}/*** 合并单元格** @param poiModel* @param content* @param index* @param i* @param sheet*/private static void get(PoiModel poiModel, String content, int index, int i, Sheet sheet) {if (poiModel.getRowIndex() != index - 1) {CellRangeAddress cra = new CellRangeAddress(poiModel.getRowIndex(), index - 1, poiModel.getCellIndex(), poiModel.getCellIndex());//在sheet里增加合并单元格sheet.addMergedRegion(cra);}/*重新记录该列的内容为当前内容,行标记改为当前行标记,列标记则为当前列*/poiModel.setContent(content);poiModel.setRowIndex(index);poiModel.setCellIndex(i);}public static void main(String[] args) throws IOException {// 此处标题的数组则对应excel的标题String[] title = {"id", "标题", "描述", "负责人", "开始时间", "名字", "年龄", "性别", "班级"};String[] field = {"id", "title", "dec", "manager", "beginTime", "name", "age", "sex", "clazz"};List<Map<String, String>> list = Lists.newArrayList();// 这边是制造一些数据,注意每个list中map的key要和标题数组中的元素一致for (int i = 0; i < 100; i++) {HashMap<String, String> map = Maps.newHashMap();if (i > 40) {if (i < 45) {map.put("id", "333");map.put("title", "美女");} else if (i > 50 && i < 55) {map.put("id", "444");map.put("title", "美男");} else {map.put("id", "444");map.put("title", "少男");}} else if (i > 25) {map.put("id", "222");map.put("title", "少女");} else if (i == 5 || i == 8) {map.put("id", "222");map.put("title", "少年");} else {map.put("id", "222");map.put("title", "青年");}map.put("dec", "都是有用的人");map.put("manager", "管理员");map.put("beginTime", "2017-02-27 11:20:26");map.put("name", "tsy");map.put("age", "28");map.put("sex", "男");if (i > 80) {if (i < 82) {map.put("clazz", "er版");} else {map.put("clazz", "");}} else {map.put("clazz", "一版");}list.add(map);}Map<String, List<Map<String, String>>> map = Maps.newHashMap();map.put("测试合并数据", list);// 模拟大数据量情况下,任务中心可分页查询接口,分批返回数据List<List<Map<String, String>>> groups = pageByNum(list, 5);// 此处数组为需要合并的列,可能有的需求是只需要某些列里面相同内容合并System.out.println(createExcel(field, title, groups, new int[]{0, 1, 2, 8}));}public static <T> List<List<T>> pageByNum(List<T> list, int pageSize) {return IntStream.range(0, list.size()).boxed().filter(t -> t % pageSize == 0).map(t -> list.stream().skip(t).limit(pageSize).collect(Collectors.toList())).collect(Collectors.toList());}}package com.example.demo.excel;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PoiModel {private String content;private String oldContent;private String primaryKey;private int rowIndex;private int cellIndex;}

3、友情提醒

    1)SXSSFSheet源码:大数据合并导出,可采用addMergedRegionUnsafe,不必要校验,大大

提高效率

public int addMergedRegion(CellRangeAddress region) {return this._sh.addMergedRegion(region);
}
public int addMergedRegionUnsafe(CellRangeAddress region) {return this._sh.addMergedRegionUnsafe(region);
}

     2)合并先判断单元格是否需要合并,可以合并再合并

CellRangeAddress cellRangeAddress = new CellRangeAddress(startRowIndex, rowIndex - 1, i, i);
if (cellRangeAddress.getNumberOfCells() > 1) {sxssfSheet.addMergedRegionUnsafe(cellRangeAddress);
}

4、后记

        1、如果有帮助到你,不胜荣幸。

        2、如果有问题,可以直接私信我,互相学习。

        3、文中难免会存在错误的地方,希望能提出来,帮助我解决它。


http://chatgpt.dhexx.cn/article/NMnabHiM.shtml

相关文章

css动画(旋转)

css动画&#xff08;旋转&#xff09; &#xff08;作者&#xff1a;张米&#xff0c;撰写时间&#xff1a;2019年2月4号&#xff09; 旋转函数rotate(n)通过指定的角度参数使元素围绕原点旋转。n是角度参数&#xff0c;用于设置参数的大小&#xff0c;参数单位是deg。参数为正…

CSS - 元素旋转动画(360度转圈)

效果图 代码 keyframes rotate {0%{-webkit-transform:rotate(0deg);}25%{-webkit-transform:rotate(90deg);}50%{-webkit-transform:rotate(180deg);}75%{-webkit-transform:rotate(270deg);}100%{-webkit-transform:rotate(360deg);} }使用 /* turn : 定义的动画名称1s : 动…

CSS——正方体360°旋转动画 效果

先看效果&#xff1a; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" c…

css3动画—旋转

css3动画旋转&#xff0c;内圆顺时针旋转&#xff0c;外圆逆时针旋转 1、html <img src"img/about-img07.png" class"circle01 circle-pst"> <img src"img/about-img06.png" class"circle02 circle-pst">2、css .circle…

CSS - 音乐盒 360° 无限循环旋转动画(元素转圈)

前言 由于 Gif 图片过小的问题&#xff0c;显得很不流畅&#xff0c;真实情况下很流畅&#xff0c;放心。 实现一个 360 无限循环旋转的动画&#xff0c;如下图所示&#xff1a; 示例代码 注意&#xff1a;通过 animation 复合属性&#xff0c;可控制动画旋转速度及其他参数。…

css旋转动画定义中心,css动画(transition/transform/animation)

在开发中&#xff0c;一个好的用户操作界面&#xff0c;总会夹杂着一些动画。css用对少的代码&#xff0c;来给用户最佳的体验感&#xff0c;下面我总结了一些css动画属性的使用方法及用例代码供大家参考&#xff0c;在不对的地方&#xff0c;希望大佬直接拍砖评论。 1 transit…

利用css3实现立体旋转动画效果

css3实行一个转动的六边形 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>body,html {width: 100%;height: 100%;}body {perspective: 1000px;}.container {/* 给容器设置一个高宽 */width: 3…

CSS实现旋转风车

CSS实现旋转风车 使用css实现旋转风车主要是运用border和css动画来实现的&#xff0c;效果图如下&#xff1a; 一、制作风车 首先观察风车是由8个相等形状大小的三角形旋转组成的&#xff0c;可以发现都是围绕一个中心点旋转组成的&#xff0c;所以我们可以先用border画出一…

CSS的动画效果-旋转

开发工具与关键技术&#xff1a;css 撰写时间&#xff1a;2020/7/7 这是简陋的小风车 当我们将鼠标移入到该区域式这个小风车就会旋转起来 这是这个小风车的代码 Html&#xff1a; Css&#xff1a; 本人只是个新手&#xff0c;只是将风车放置Ul标签内&#xff0c;设置ul标签的…

Css动画效果旋转图片

这次给大家讲解一个有趣的css动画效果哈&#xff0c;那就是旋转图片成一朵花型。 第一步依旧是把img标签敲出来然后把图片放上去。 2.然后开始敲打css样式和效果&#xff0c;先给个class设置一下样式加个定位&#xff0c;因为动画效果需要个定 位才能有效果显示出来。…

旋转的css动画效果

需要补习动画属性的可以查看这个文章 效果图 css旋转代码 .image {width: 250px;height: 250px;border-radius: 50%;//定义动画名,持续时间,动画状态,以及持续运行animation: rotate 15s infinite linear;//控制暂停和播放animation-play-state: play;}keyframes rotate {form…

CSS3 旋转动画

效果图 用到图片&#xff1a; 实现&#xff1a; <body><div class"wrap"><image class"figure" src"./staitc/images/figure.png" /><imageclass"circle circle-inner"src"./staitc/images/circle-in…

纯css图片自动旋转动画

css能否实现图片自动旋转动画呢&#xff1f;答案当然是肯定的&#xff0c; 首先看下效果&#xff1a; HTML代码 <img src"https://gimg2.baidu.com/image_search/srchttp%3A%2F%2Fimg.yipic.cn%2Fthumb%2Fda6639c9%2F814ac8bc%2Ff0e92a5b%2Fedda1715%2Fbig_da6639c9…

动画css ---无限旋转

前言&#xff1a; 接口返回数据&#xff0c;处理数据后&#xff0c;需要根据状态显示对应的图片 如果是状态为运行中&#xff0c;图片对应的icon图片需要沿中心点旋转起来&#xff08;加一个带有旋转的class类&#xff09; 图片素材 <!DOCTYPE html> <html lang"…

CSS——动画{旋转按钮}

前面我们一直在学习样式&#xff0c;学习布局&#xff0c;什么浮动啊&#xff0c;定位呀&#xff0c;还有弹性盒子&#xff0c;那么今天我们来看一点不一样的——动画&#xff01; 文章目录 前言一、动画是什么&#xff1f;二、动画的属性 1.过渡2.实际应用和代码演示总结 前言…

css实现加载旋转动画

先看效果&#xff1a; 完整代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport&q…

CSS基础学习——动画

一、CSS3 2D变形&#xff08;利用Transfrom方法&#xff09; 1、rotate&#xff08;angle&#xff09; 元素顺时针旋转给定的角度。允许负值&#xff0c;元素将逆时针旋转。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8…

深度学习与神经网络

文章目录 引言1. 神经网络1.1 什么是神经网络1.2 神经元1.3 多层神经网络 2. 激活函数2.1 什么是激活函数2.2 激活函数的作用2.3 常用激活函数解析2.4 神经元稀疏 3. 设计神经网络3.1 设计思路3.2 对隐含层的感性认识 4. 深度学习4.1 什么是深度学习4.2 推理和训练4.3 训练的相…

【深度学习】你心目中 idea 最惊艳的深度学习领域论文是哪篇?

科研路上我们往往会读到让自己觉得想法很惊艳的论文&#xff0c;心中对不同的论文也会有一个排名。 我们来看看各路大神是怎么评价的。 论文链接 https://arxiv.org/abs/1410.3916 关于计算机视觉领域&#xff0c;taokongcn分享了几个重要的工作。 1. Fully Convolutional Netw…

深度学习笔记(三)Cv方向

一.批量归一化和残差网络 批量归一化 对输入的标准化&#xff08;浅层模型&#xff09; 处理后的任意一个特征在数据集中所有样本上的均值为0、标准差为1。 标准化处理输入数据使各个特征的分布相近。 批量归一化&#xff08;深度模型&#xff09; 利用小批量上的均值和标准差…