在Java中使用Apache POI保留Excel样式合并多个工作簿
背景
在日常工作中,我们经常需要将多个Excel文件合并成一个,同时保留原有的样式和格式。Apache POI是一个流行的Java库,用于读取和写入Microsoft Office格式的文件,包括Excel。然而,仅仅使用Apache POI的基本功能进行合并操作往往会导致样式的丢失,这对于需要保留格式信息的场景来说是不够的。常见的应用场景如钉钉的打卡数据只能一个一个的表格导出,接下来的文章就恰好可以运用到这样的场景中。
本文将探讨如何在合并Excel文件时保留原表格的样式,并提供一个改进的代码示例,以便更好地处理这个问题。
问题描述
在合并多个Excel文件时,常见的挑战之一是保留每个文件中的单元格样式。这些样式可能包括字体、颜色、边框、填充等。使用Apache POI的基本方法复制单元格时,只会复制内容,而不会保留这些样式。
解决方案
要解决这个问题,我们需要使用Apache POI的更高级功能来复制单元格样式。这涉及到创建样式映射,以便在合并过程中跟踪和复制每个单元格的样式。
以下是实现这一功能的关键步骤:
创建样式映射:使用一个映射结构(如HashMap)来存储从原始工作簿的CellStyle到合并工作簿的XSSFCellStyle的映射。这可以确保我们不会为相同的样式重复创建新的XSSFCellStyle对象。
遍历工作簿和工作表:对于要合并的每个Excel文件,遍历其中的每个工作簿和工作表。
复制行和单元格:对于每个工作表中的行和单元格,创建新的行和单元格,并复制内容。
复制样式:在复制单元格时,检查样式映射是否已经包含了该单元格的样式。如果没有,从原始工作簿中克隆样式,并将其添加到映射中。然后,将新克隆的样式应用到新创建的单元格上。
写入合并后的工作簿:最后,将合并后的工作簿写入输出文件。
代码示例
下面是一个示例代码,展示了如何实现上述解决方案:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.0</version>
</dependency>
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.0</version>
</dependency>
案例一:多个excel合成一个表格
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.*;
import java.util.ArrayList;
import java.util.List;public class ExcelMerger {public static void main(String[] args) {List<String> fileNames = new ArrayList<>();fileNames.add("H:\\time\\_打卡时间表1.xlsx");fileNames.add("H:\\time\\_打卡时间表2.xlsx");fileNames.add("H:\\time\\_打卡时间表3.xlsx");// 添加更多文件路径...String outputFileName = "H:\\time\\merged_excel_file.xlsx";try{InputStream inputStream;Workbook workbook;OutputStream outputStream = new FileOutputStream(outputFileName);XSSFWorkbook mergedWorkbook = new XSSFWorkbook();int j = 0;for (String fileName : fileNames) {inputStream = new FileInputStream(fileName);workbook = WorkbookFactory.create(inputStream);j ++;for (int i = 0; i < workbook.getNumberOfSheets(); i++) {Sheet sheet = workbook.getSheetAt(i);try {mergedWorkbook.createSheet(sheet.getSheetName() + i + j);}catch (Exception e){System.out.println(1);}copySheet(mergedWorkbook.getSheet(sheet.getSheetName() + i + j), sheet);}}mergedWorkbook.write(outputStream);} catch (IOException e) {e.printStackTrace();}}private static void copySheet(Sheet newSheet, Sheet sheet) {int rowCount = sheet.getLastRowNum() - sheet.getFirstRowNum();for (int i = 0; i <= rowCount; i++) {Row newRow = newSheet.createRow(i);Row currentRow = sheet.getRow(i);copyRow(newRow, currentRow);}}private static void copyRow(Row newRow, Row currentRow) {if (currentRow == null) {return;}int cellCount = currentRow.getLastCellNum();for (int i = 0; i < cellCount; i++) {Cell currentCell = currentRow.getCell(i);if (currentCell == null) {continue;}Cell newCell = newRow.createCell(i);copyCell(newCell, currentCell);}}private static void copyCell(Cell newCell, Cell currentCell) {newCell.setCellType(currentCell.getCellType());switch (currentCell.getCellType()) {case STRING:newCell.setCellValue(currentCell.getStringCellValue());break;case BOOLEAN:newCell.setCellValue(currentCell.getBooleanCellValue());break;case NUMERIC:newCell.setCellValue(currentCell.getNumericCellValue());break;case FORMULA:newCell.setCellFormula(currentCell.getCellFormula());break;default:break;}}
}
案例二:多个表格合成一个sheet
//初始化一个XSSFWorkbook实例作为合并后的工作簿,并创建一个名为"Merged Sheet"的新sheet。
//遍历每个要合并的Excel文件,并对每个文件中的每个sheet进行迭代。
//对于每个sheet中的每一行,都创建一个新的行并复制到合并后的sheet中。
//在复制行时,同时复制每个单元格的内容,包括字符串、布尔值、数字和公式。
//最后,将合并后的工作簿写入输出文件。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.*;
import java.util.ArrayList;
import java.util.List; public class ExcelMerger {public static void main(String[] args) { List<String> fileNames = new ArrayList<>();fileNames.add("H:\\time\\_打卡时间表1.xlsx");fileNames.add("H:\\time\\_打卡时间表2.xlsx");fileNames.add("H:\\time\\_打卡时间表3.xlsx");// 添加更多文件路径...String outputFileName = "H:\\time\\merged_excel_file.xlsx";try {InputStream inputStream; Workbook workbook;FileOutputStream outputStream = new FileOutputStream(outputFileName);XSSFWorkbook mergedWorkbook = new XSSFWorkbook(); Sheet mergedSheet = mergedWorkbook.createSheet("Merged Sheet"); int rowNum = 0; // 用于新sheet中的行号 for (String fileName : fileNames) { inputStream = new FileInputStream(fileName); workbook = WorkbookFactory.create(inputStream); for (int i = 0; i < workbook.getNumberOfSheets(); i++) { Sheet sheet = workbook.getSheetAt(i); for (Row row : sheet) { Row newRow = mergedSheet.createRow(rowNum++); copyRow(newRow, row); } } } mergedWorkbook.write(outputStream); } catch (IOException e) { e.printStackTrace(); } } private static void copyRow(Row newRow, Row currentRow) { if (currentRow == null) { return; } for (Cell cell : currentRow) { Cell newCell = newRow.createCell(cell.getColumnIndex(), cell.getCellType()); switch (cell.getCellType()) { case STRING: newCell.setCellValue(cell.getStringCellValue()); break; case BOOLEAN: newCell.setCellValue(cell.getBooleanCellValue()); break; case NUMERIC: newCell.setCellValue(cell.getNumericCellValue()); break; case FORMULA: newCell.setCellFormula(cell.getCellFormula()); break; case BLANK: // Handle blank cells as needed break; default: break; } } }
}
案例三:多个表格合成一个sheet,并保留样式
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class ExcelMergerWithStyles { public static void main(String[] args) { List<String> fileNames = new ArrayList<>();fileNames.add("H:\\time\\_打卡时间表1.xlsx");fileNames.add("H:\\time\\_打卡时间表2.xlsx");fileNames.add("H:\\time\\_打卡时间表3.xlsx");// 添加更多文件路径...String outputFileName = "H:\\time\\merged_excel_file.xlsx";try {InputStream inputStream;Workbook workbook;FileOutputStream outputStream = new FileOutputStream(outputFileName);XSSFWorkbook mergedWorkbook = new XSSFWorkbook(); Sheet mergedSheet = mergedWorkbook.createSheet("Merged Sheet"); int rowNum = 0; // 用于新sheet中的行号 // 用于存储已创建样式的映射,以避免重复创建相同的样式 Map<CellStyle, XSSFCellStyle> styleMap = new HashMap<>();for (String fileName : fileNames) { inputStream = new FileInputStream(fileName); workbook = WorkbookFactory.create(inputStream); for (int i = 0; i < workbook.getNumberOfSheets(); i++) { Sheet sheet = workbook.getSheetAt(i); for (Row row : sheet) { Row newRow = mergedSheet.createRow(rowNum++); copyRowWithStyles(newRow, row, mergedWorkbook, styleMap); } } } mergedWorkbook.write(outputStream); } catch (IOException e) { e.printStackTrace(); } } private static void copyRowWithStyles(Row newRow, Row currentRow, XSSFWorkbook mergedWorkbook, Map<CellStyle, XSSFCellStyle> styleMap) { if (currentRow == null) { return; } for (Cell cell : currentRow) { Cell newCell = newRow.createCell(cell.getColumnIndex(), cell.getCellType()); copyCellContent(cell, newCell); // 复制样式 CellStyle cellStyle = cell.getCellStyle(); XSSFCellStyle newCellStyle = styleMap.get(cellStyle); if (newCellStyle == null) { newCellStyle = mergedWorkbook.createCellStyle(); newCellStyle.cloneStyleFrom(cellStyle); styleMap.put(cellStyle, newCellStyle); } newCell.setCellStyle(newCellStyle); } } private static void copyCellContent(Cell source, Cell target) { switch (source.getCellType()) { case STRING: target.setCellValue(source.getStringCellValue()); break; case BOOLEAN: target.setCellValue(source.getBooleanCellValue()); break; case NUMERIC: target.setCellValue(source.getNumericCellValue()); break; case FORMULA: target.setCellFormula(source.getCellFormula()); break; case BLANK: // Handle blank cells as needed break; default: break; } }
}
总结
通过扩展Apache POI库的基本功能,我们可以实现在合并Excel文件时保留原表格样式的能力。这涉及到创建样式映射,遍历工作簿和工作表,复制行、单元格和样式,并最终写入合并后的工作簿。虽然这个过程可能比简单的合并更为复杂,但它提供了更大的灵活性,允许我们根据需要定制合并后的Excel文件的样式和格式。
对于需要处理大量Excel文件或具有复杂样式的场景,这种方法可能需要进行进一步的优化和错误处理。但无论如何,它提供了一个很好的起点,用于在Java中实现具有样式保留功能的Excel合并操作。
相关文章:
在Java中使用Apache POI保留Excel样式合并多个工作簿
背景 在日常工作中,我们经常需要将多个Excel文件合并成一个,同时保留原有的样式和格式。Apache POI是一个流行的Java库,用于读取和写入Microsoft Office格式的文件,包括Excel。然而,仅仅使用Apache POI的基本功能进行…...
Nomachine远程黑屏通用处理方法
Nomachine远程黑屏通用处理方法 文章目录 前言正文解决步骤 总结 前言 NoMachine是一种远程桌面软件,它允许用户通过互联网或局域网连接到远程计算机,并在本地计算机上使用远程计算机的桌面环境和应用程序。它提供了高性能的图形渲染和低延迟的响应&…...
基于51单片机数控直流电压源proteus仿真LCD显示+程序+设计报告+讲解视频
基于51单片机数控直流电压源proteus仿真LCD显示( proteus仿真程序设计报告讲解视频) 仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0072 讲解视频 基于51单片机数控直流电压源proteus仿真程序…...
[Linux]文件缓冲区
文件fd 输出重定向除了用dup2()改变数组下标外,还可以用命令来完成 所有的命令执行,都必须有操作系统将其运行起来变成进程,然后根据>>, <<来判断是输入重定向,还是输出重定向。 缓冲区 之所以有缓冲区࿰…...
ARM:按键中断
key_inc.c #include"key_inc.h"void key1_it_config(){//使能GPIOF外设时钟RCC->MP_AHB4ENSETR | (0x1<<5);//将PF9设置为输入模式GPIOF->MODER & (~(0x3<<18));//设置由PF9管脚产生EXTI9事件EXTI->EXTICR3 & (~(0XFF<<8));EXTI…...
JavaScript高级(五)--柯西化函数
柯里化 定义 首先,我们参照维基百科给出的定义: 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数…...
带3090显卡的Linux服务器上部署SDWebui
背景 一直在研究文生图,之前一直是用原始模型和diffuser跑SD模型,近来看到不少比较博主在用 SDWebui,于是想着在Linux服务器上部署体验一下,谁知道并没有想象的那么顺利,还是踩了不少坑。记录一下过程,也许…...
37、Linux中Xsync数据同步备份工具
37、Linux中Xsync数据同步备份工具 一、介绍二、配置集群hostname三、修改xsync文件四、赋权五、安装Rsync六、验证一七、配置免密登录1、生成rsa密钥2、copy机器自身公钥到目标机器3、.ssh/文件目录赋权 八、验证二 ⚠️ 注:本文全程在普通用户下操作,…...
网络基础:构建你的数字世界之桥
I. 引言 A. 什么是网络? 网络是由各种电子设备通过通信线路或无线电波连接起来,以便彼此之间进行数据交换和资源共享的系统。在当今数字化时代,网络已经成为我们生活和工作中不可或缺的一部分,无论是互联网、局域网还是无线通信…...
Python 全栈系列236 rabbit_agent搭建
说明 通过rabbit_agent, 以接口方式实现对队列的标准操作,将pika包在微服务内,而不必在太多地方重复的去写。至少在服务端发布消息时,不必再去考虑这些问题。 在分布式任务的情况下,客户端本身会启动一个持续监听队列的客户端服…...
管理自由,体验简单,使用安全 | 详解威联通全套多用户多权限管理方案【附TS-466C产品介绍】
管理自由,体验简单,使用安全 | 详解威联通全套多用户多权限管理方案【附TS-466C产品介绍】 哈喽小伙伴们好,我是Stark-C~。今天我们来解决一个之前评论区多次被提及的问题--多用户权限管理。 对于我们NAS用户来说,基本都会面临这…...
【Redis】优惠券秒杀
全局唯一ID 全局唯一ID生成策略: UUIDRedis自增snowflake算法数据库自增 Redis自增ID策略:每天一个key,方便统计订单量ID构造是 时间戳 计数器 Component public class RedisIdWorker {// 2024的第一时刻private static final long BEGIN…...
【几何】平面方程
文章目录 平面方程一般式截距式点法式法线式 平面方程 平面方程是用一个方程来表示平面,平面上的所有点代入方程,方程都成立。因为用法的不同,平面方程一般有四种表现形式。 一般式 设 n ⃗ ( A , B , C ) \vec n(A,B,C) n (A,B,C) 为平…...
macOS访问samba文件夹的正确姿势,在哪里更改“macOS的连接身份“?还真不好找!
环境:路由器上需要身份认证的Mini NAS macOS Sonoma 14 这是一个非常简单的问题,但解决方法却藏得比较深,不够直观,GPT也没有给出明确的解决提示,特意记录一下。 macOS很多地方都很自动,有时候让人找不到设…...
linux进程切换
内核堆栈:每个进程在内核模式下运行时都有自己的内核堆栈。这个堆栈保存了进程在内核模式下的运行状态,包括函数调用时传递的参数、局部变量和返回地址等。 用户态与内核态:进程通常在用户态下运行,当执行系统调用或响应中断时进…...
spring boot 如何升级 Tomcat 版本
在Spring Boot应用程序中升级内嵌的Tomcat版本通常涉及以下几个步骤: 1. 确定当前使用的Tomcat版本 首先,你需要确定你的Spring Boot应用程序当前使用的Tomcat版本。这可以通过查看项目的pom.xml或build.gradle文件来完成,其中会列出所有的…...
sentinel中StatisticSlot数据采集的原理
StatisticSlot数据采集的原理 时间窗口 固定窗口 在固定的时间窗口内,可以允许固定数量的请求进入;超过数量就拒绝或者排队,等下一个时间段进入, 如下图 时间窗长度划分为1秒 单个时间窗的请求阈值为3 上述存在一个问题, 假如9:18:04:…...
图像去噪与增强技术
图像去噪与增强技术是数字图像处理领域中的两个重要方面,它们分别关注消除图像中的噪声和改善图像的质量。 图像去噪技术的主要目的是从受噪声干扰的图像中去除不必要的随机信号,以恢复图像的真实内容。这对于图像的进一步分析和理解至关重要。去噪技术包…...
SpringJPA 做分页条件查询
前言: 相信小伙伴们的项目很多都用到SpringJPA框架的吧,对于单表的增删改查利用jpa是很方便的,但是对于条件查询并且分页 是不是很多小伙伴不经常写到. 今天我整理了一下在这里分享一下. 话不多说直接上代码: Controller: RestController public class ProductInstanceContr…...
[Java基础揉碎]单例模式
目录 什么是设计模式 什么是单例模式 饿汉式与懒汉式 饿汉式vs懒汉式 懒汉式存在线程安全问题 什么是设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模式就像是经典的棋谱&am…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
