当前位置: 首页 > news >正文

JAVA解析Excel复杂表头

废话不多说,直接上源码。前后端都有哦~~~~~~~~

能帮到你记得点赞收藏哦~~~~~~~~~

后端: 

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.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** @Description:Excel工具类(复杂表头解析)* @Author: sh* @Date: 2025/1/2 09:29*/
public class ExcelComplexUtil {/*** 导入Excel文件,逐行读取数据,数据格式二维数组* @param filePath* @param sheetIndex* @return* @throws IOException*/public String[][] importExcel(String filePath, int sheetIndex) throws IOException {List<String[]> dataList = new ArrayList<>();try (FileInputStream fis = new FileInputStream(new File(filePath));Workbook workbook = new XSSFWorkbook(fis)) {Sheet sheet = workbook.getSheetAt(sheetIndex); // 获取第一个工作表// 获取表头行Row headerRow = checkHeaderRow(sheet);if (headerRow != null) {//封装表头数据warpHeaderData(headerRow, dataList);} else {throw new RuntimeException("Excel 文件中没有找到表头行,请修改表格");}// 读取数据行for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {Row row = sheet.getRow(rowIndex);if (row == null) {continue; // 跳过空行}//封装行数据warpRowData(headerRow, row, dataList);}}// 将 List<String[]> 转换为 String[][] 数组String[][] result = new String[dataList.size()][];for (int i = 0; i < dataList.size(); i++) {result[i] = dataList.get(i);}return result; // 返回二维数组}/*** 检查表头行,表头行必须在前10行中* @param sheet* @return*/private Row checkHeaderRow(Sheet sheet) {int i = 0;Row headerRow = null;while (i < 10) {headerRow = sheet.getRow(i);if (headerRow != null) {break;}i++;}return headerRow;}/*** 数据遍历* @param headerRow* @param dataList* @throws IOException*/public void warpHeaderData(Row headerRow, List<String[]> dataList) {int columnCount = headerRow.getPhysicalNumberOfCells();short lastCellNum = headerRow.getLastCellNum();String[] data = new String[columnCount]; // 创建一维数组存储表头数据for (int colIndex = 0; colIndex < columnCount; colIndex++) {Cell cell = headerRow.getCell(lastCellNum - columnCount + colIndex);String cellValue = (cell != null) ? cell.toString() : ""; // 处理空单元格data[colIndex] = cellValue; // 将单元格值放入表头数组中}dataList.add(data); // 将表头数组添加到列表中}public void warpRowData(Row headerRow, Row row, List<String[]> dataList) {int columnCount = headerRow.getPhysicalNumberOfCells();short lastCellNum = headerRow.getLastCellNum();String[] data = new String[columnCount]; // 创建一维数组存储表头数据for (int colIndex = 0; colIndex < columnCount; colIndex++) {Cell cell = row.getCell(lastCellNum - columnCount + colIndex);String cellValue = (cell != null) ? cell.toString() : ""; // 处理空单元格data[colIndex] = cellValue; // 将单元格值放入表头数组中}dataList.add(data); // 将表头数组添加到列表中}/*** 获取excel中所有合并单元格* @param filePath* @param sheetIndex* @throws IOException*/public List<MergedCell> checkMergedCells(String filePath, int sheetIndex) throws IOException {try (FileInputStream fis = new FileInputStream(new File(filePath));Workbook workbook = new XSSFWorkbook(fis)) {Sheet sheet = workbook.getSheetAt(sheetIndex); // 获取第一个工作表int numberOfMergedRegions = sheet.getNumMergedRegions(); // 获取合并单元格的数量List<MergedCell> mergedCellArray = new ArrayList<>();for (int i = 0; i < numberOfMergedRegions; i++) {MergedCell mergedCell = new MergedCell();CellRangeAddress range = sheet.getMergedRegion(i); // 获取合并单元格区域mergedCell.setRange(range.formatAsString());// 获取合并单元格区域的起始单元格int firstRow = range.getFirstRow();int firstCol = range.getFirstColumn();// 获取合并单元格的内容Row row = sheet.getRow(firstRow);Cell cell = row.getCell(firstCol);String cellValue = (cell != null) ? cell.toString() : ""; // 处理空单元格mergedCell.setValue(cellValue);mergedCellArray.add(mergedCell);}return mergedCellArray;}}/*** 检查特定单元格是否是合并单元格* @param sheet* @param row* @param col* @return*/private boolean isMergedCell(Sheet sheet, int row, int col) {int numberOfMergedRegions = sheet.getNumMergedRegions();for (int i = 0; i < numberOfMergedRegions; i++) {CellRangeAddress range = sheet.getMergedRegion(i);if (range.isInRange(row, col)) {return true; // 如果该单元格在合并区内,返回 true}}return false; // 如果不在任何合并区内,返回 false}class MergedCell {private String range;private String value;public String getRange() {return range;}public void setRange(String range) {this.range = range;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}@Overridepublic String toString() {return "MergedCell{" +"range='" + range + '\'' +", value='" + value + '\'' +'}';}}//    public static void main(String[] args) {
//        String filePath = "/ceshi/ceshi.xlsx"; // Excel 文件路径
//        try {
//            String[][] strings = importExcel(filePath, 0);
//            for (String[] row : strings) {
//                System.out.println(String.join(", ", row)); // 以逗号为分隔符打印每一行
//            }
//            List<MergedCell> mergedCells = checkMergedCells(filePath, 0);
//            for (MergedCell row : mergedCells) {
//                System.out.println(row); // 以逗号为分隔符打印每一行
//            }
//
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
}

前端:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>合并单元格查看器</title><style>table {border-collapse: collapse;width: 500px; /* 设置表格宽度 */}td {border: 1px solid black;padding: 0; /* 清除内边距 */text-align: center;vertical-align: middle; /* 垂直居中 */height: 50px; /* 设置每个单元格的高度 */}.merged {background-color: #f0f0f0; /* 合并单元格的背景颜色 */font-weight: bold; /* 合并单元格字体加粗 */}</style>
</head>
<body><div id="app"><h1>合并单元格查看器</h1><table><tr v-for="(row, rowIndex) in tableData" :key="rowIndex"><tdv-for="(cell, colIndex) in row":key="colIndex"v-if="!isCellOccupied(rowIndex, colIndex)":rowspan="getRowSpan(rowIndex, colIndex)":colspan="getColSpan(rowIndex, colIndex)":class="{ merged: isMergedCell(rowIndex, colIndex) }">{{ getCellValue(rowIndex, colIndex) }}</td></tr></table></div><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script><script>new Vue({el: '#app',data() {return {mergedCells: [{ range: "C1:D1", value: "合并单元格 1" },{ range: "B2:C2", value: "合并单元格 2" },{ range: "B3:C3", value: "合并单元格 " },{ range: "A2:A3", value: "合并单元格 " },{ range: "F2:F3", value: "合并单元格 " } ],normalData: [["监督员地区", "总数", "有效", "", "有效政治类", ""],["平谷区监督员", "244", "", "197", "28", "169"],["", "244", "", "197", "28", ""],["数据 16", "数据 17", "数据 18", "数据 19", "数据 20"],["数据 21", "数据 22", "数据 23", "数据 24", "数据 25"],["", "", "", "", ""]],occupiedCells: []};},computed: {// 生成完整表格数据tableData() {const rows = this.normalData.length;const cols = this.normalData[0].length;const emptyTable = Array.from({ length: rows }, () => Array(cols).fill(null));// 填充合并单元格this.mergedCells.forEach(({ range, value }) => {const [start, end] = range.split(':');const startRow = parseInt(start.match(/\d+/)[0]) - 1;const startCol = start.charCodeAt(0) - 65;const endRow = parseInt(end.match(/\d+/)[0]) - 1;const endCol = end.charCodeAt(0) - 65;// 填充合并单元格的位置for (let r = startRow; r <= endRow; r++) {for (let c = startCol; c <= endCol; c++) {if (r === startRow && c === startCol) {emptyTable[r][c] = value; // 合并单元格的值} else {this.occupiedCells[r] = this.occupiedCells[r] || [];this.occupiedCells[r][c] = true; // 标记占用}}}});// 填充普通单元格的数据this.normalData.forEach((row, r) => {row.forEach((cell, c) => {if (!this.occupiedCells[r] || !this.occupiedCells[r][c]) {emptyTable[r][c] = cell;}});});return emptyTable;}},methods: {isCellOccupied(rowIndex, colIndex) {return this.occupiedCells[rowIndex] && this.occupiedCells[rowIndex][colIndex];},getCellValue(rowIndex, colIndex) {return this.tableData[rowIndex][colIndex];},getRowSpan(rowIndex, colIndex) {let rowspan = 1;const firstValue = this.getCellValue(rowIndex, colIndex);const mergedCell = this.mergedCells.find(({ range }) => {const [start, end] = range.split(':');const startRow = parseInt(start.match(/\d+/)[0]) - 1;const startCol = start.charCodeAt(0) - 65;const endRow = parseInt(end.match(/\d+/)[0]) - 1;const endCol = end.charCodeAt(0) - 65;return rowIndex === startRow && colIndex === startCol;});if (mergedCell) {const [start, end] = mergedCell.range.split(':');const startRow = parseInt(start.match(/\d+/)[0]) - 1;const endRow = parseInt(end.match(/\d+/)[0]) - 1;rowspan = endRow - startRow + 1;}return rowspan;},getColSpan(rowIndex, colIndex) {let colspan = 1;const firstValue = this.getCellValue(rowIndex, colIndex);const mergedCell = this.mergedCells.find(({ range }) => {const [start, end] = range.split(':');const startRow = parseInt(start.match(/\d+/)[0]) - 1;const startCol = start.charCodeAt(0) - 65;const endRow = parseInt(end.match(/\d+/)[0]) - 1;const endCol = end.charCodeAt(0) - 65;return rowIndex === startRow && colIndex === startCol;});if (mergedCell) {const [start, end] = mergedCell.range.split(':');const startCol = start.charCodeAt(0) - 65;const endCol = end.charCodeAt(0) - 65;colspan = endCol - startCol + 1;}return colspan;},isMergedCell(rowIndex, colIndex) {return this.mergedCells.some(({ range }) => {const [start, end] = range.split(':');const startRow = parseInt(start.match(/\d+/)[0]) - 1;const startCol = start.charCodeAt(0) - 65;return rowIndex === startRow && colIndex === startCol;});}}});</script>
</body>
</html>

相关文章:

JAVA解析Excel复杂表头

废话不多说&#xff0c;直接上源码。前后端都有哦&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 能帮到你记得点赞收藏哦&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#…...

jmeter 中 BeanShell 预处理程序、JSR223后置处理程序使用示例

1. 各个组件如何新建的&#xff1f; 2. "http请求" 组件内容样例&#xff1a; "消息体数据" 源码&#xff1a; {"task_tag": "face_detect","image_type": "base64","extra_args": [{"model"…...

我的创作纪念日——《惊变128天》

我的创作纪念日——《惊变128天》 机缘收获日常成就憧憬 机缘 时光飞逝&#xff0c;转眼间&#xff0c;我已在这条创作之路上走过了 128 天。回顾起 2024 年 8 月 29 日&#xff0c;我满怀忐忑与期待&#xff0c;撰写了第一篇技术博客《讲解LeetCode第1题&#xff1a;两数之和…...

vuedraggable 选项介绍

vuedraggable 是基于 SortableJS 的 Vue 组件&#xff0c;提供了丰富的选项来定制拖拽行为。以下是 vuedraggable 常用的选项和它们的详细说明&#xff1a; 常用选项介绍 group 配置拖拽分组。多个列表可以共享同一个分组&#xff0c;允许它们之间的项目互相拖拽。 group: { na…...

微信小程序获取后端数据

在小程序中获取后端接口数据 通常可以使用 wx.request 方法&#xff0c;以下是一个基本示例&#xff1a; // pages/index/index.js Page({data: {// 用于存储后端返回的数据resultData: [] },onLoad() {this.fetchData();},fetchData() {wx.request({url: https://your-backe…...

ThreadLocal` 的工作原理

ThreadLocal 的工作原理&#xff1a; ThreadLocal 是 Java 提供的一个类&#xff0c;它用于为每个线程提供独立的变量副本。也就是说&#xff0c;多个线程访问同一个 ThreadLocal 变量时&#xff0c;每个线程看到的值都是不同的&#xff0c;相互隔离&#xff0c;互不干扰。 T…...

数据挖掘教学指南:从基础到应用

数据挖掘教学指南&#xff1a;从基础到应用 引言 数据挖掘是大数据时代的核心技术之一&#xff0c;它从大量数据中提取有用信息和知识。本教学文章旨在为学生和初学者提供一个全面的数据挖掘学习指南&#xff0c;涵盖数据挖掘的基本概念、流程、常用技术、工具以及教学建议。…...

大模型搜索引擎增强问答demo-纯python实现

流程概览 本文使用python语言,实现了大模型搜索引擎增强问答demo。 大模型搜索引擎增强问答定义:根据问题搜索得到相关内容,拼接prompt=问题+搜索结果,将这个prompt传入大模型,得到最终的结果。 优势在于搜索引擎可以返回实时性信息,例如明日双色球开奖信息、最新八卦…...

【C语言程序设计——选择结构程序设计】按从小到大排序三个数(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 编程要求 相关知识 1. 选择结构 2. 主要语句类型 3. 比较操作 4. 交换操作 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;从键盘上输入三个数&#xff0c;请按从小到大的顺序排序并打印输出排序后的结果。 编程要求 根据提示…...

简洁安装配置在Windows环境下使用vscode开发pytorch

简洁安装配置在Windows环境下使用vscode开发pytorch 使用anaconda安装pytorch&#xff0c;通过vscode集成环境开发pytorch 下载 anaconda 下载网址&#xff0c;选择对应系统的版本 https://repo.anaconda.com/archive/ windows可以选择Anaconda3-2024.10-1-Windows-x86_64.e…...

conda安装及demo:SadTalker实现图片+音频生成高质量视频

1.安装conda 下载各个版本地址&#xff1a;https://repo.anaconda.com/archive/ win10版本&#xff1a; Anaconda3-2023.03-1-Windows-x86_64 linux版本&#xff1a; Anaconda3-2023.03-1-Linux-x86_64 Windows安装 环境变量 conda -V2.配置conda镜像源 安装pip conda…...

【面试】后端开发面试中常见数据结构及应用场景、原理总结

在后端开发面试中&#xff0c;常见的数据结构包括数组、链表、栈、队列、二叉树、平衡树、堆、图和哈希表等。以下是这些数据结构的总结&#xff0c;包括它们的应用场景、优缺点。 常见数据结构及其应用场景 数据结构应用场景数组存储固定大小的数据集合&#xff0c;如学生成…...

141.《mac m系列芯片安装mongodb详细教程》

文章目录 下载从官网下载安装包 下载后双击解压出文件夹安装文件名修改为 mongodb配置data存放位置和日志log的存放位置启动方式一方式二方式二:输入mongo报错以及解决办法 本人电脑 m2 pro,属于 arm 架构 下载 官网地址: mongodb官网 怎么查看自己电脑应该下载哪个版本,输入…...

Java 23 集合框架详解:ArrayList、LinkedList、Vector

&#x1f4da; Java 23 集合框架详解&#xff1a;ArrayList、LinkedList、Vector 在 Java 集合框架中&#xff0c;ArrayList、LinkedList 和 Vector 是三种最常用的 List 接口实现类。它们都可以存储有序的、可重复的元素&#xff0c;但它们在 底层实现、性能 和 多线程安全 等…...

03、MySQL安全管理和特性解析(DBA运维专用)

03、MySQL安全管理和特性解析 本节主要讲MySQL的安全管理、角色使用、特定场景下的数据库对象、各版本特性以及存储引擎 目录 03、MySQL安全管理和特性解析 1、 用户和权限管理 2、 MySQL角色管理 3、 MySQL密码管理 4、 用户资源限制 5、 忘记root密码处理办法 6、 SQ…...

创建型模式5.单例模式

创建型模式 工厂方法模式&#xff08;Factory Method Pattern&#xff09;抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;建造者模式&#xff08;Builder Pattern&#xff09;原型模式&#xff08;Prototype Pattern&#xff09;单例模式&#xff08;Singleto…...

用户界面软件02

基于表单的用户界面 在“基于表单的用户界面”里面&#xff0c;用户开始时选中某个业务处理&#xff08;模块&#xff09;&#xff0c;然后应用程序就使用一系列的表单来引导用户完成整个处理过程。大型机系统上的大部分用户界面都是这样子的。[Cok97]中有更为详细的讨论。 面…...

VTK 鼠标+键盘重构

1、鼠标事件 如果有鼠标事件处理等相应的需求,可以重写该事件。 void OnMouseMove() override; //鼠标移动事件 void OnLeftButtonDown() override;//左键按下事件 void OnLeftButtonUp() override;//左键抬起事件 void OnMiddleButtonDown() override;//滚轮按下事件 …...

go语言处理JSON数据详解

一、结构体与json之间的转换 Go语言处理JSON数据通常涉及到将JSON数据解析成Go结构体,或者将Go结构体序列化为JSON格式。Go提供了内置的encoding/json包来实现这些操作。下面详细介绍如何在Go中处理JSON数据。 1. Go结构体与JSON映射 Go语言的encoding/json包可以将JSON数据…...

基于gin一个还算比较优雅的controller实现

看了两天时间的go&#xff0c;对于go的编码风格还不是很了解&#xff0c;但是了解到go并未有Java那样成体系的编码风格规范&#xff0c;所以自己浅尝试了一下&#xff0c;风格无对错&#xff0c;欢迎交流讨论&#xff5e; controller层&#xff1a; package …...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...