当前位置: 首页 > 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 …...

猫抓插件终极指南:简单三步下载网页所有视频音频

猫抓插件终极指南&#xff1a;简单三步下载网页所有视频音频 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾经在网上看到一个精彩的视频想…...

BLE与WiFi技术演进对比:从室内定位到物联网应用

1. BLE与WiFi的技术演进史&#xff1a;从基础通信到智能物联 记得2013年我第一次用蓝牙4.0手环时&#xff0c;充一次电能撑半个月&#xff0c;当时就觉得这技术要火。十年后的今天&#xff0c;当我用手机查找AirTag精准定位到沙发缝里的钥匙时&#xff0c;才真正体会到无线通信…...

internlm2-chat-1.8b在教育场景应用:自动批改作文+生成习题的AI助教落地案例

internlm2-chat-1.8b在教育场景应用&#xff1a;自动批改作文生成习题的AI助教落地案例 想象一下&#xff0c;一位语文老师深夜还在批改堆积如山的作文本&#xff0c;既要圈出错别字、病句&#xff0c;又要写评语、给建议&#xff0c;常常忙到深夜。另一边&#xff0c;数学老师…...

Enhancing Encrypted Traffic Classification with RNN and ResNet: A Spatiotemporal Feature Fusion Appr

1. 当加密流量遇上时空特征提取 第一次看到加密流量分类这个课题时&#xff0c;我正对着满屏的十六进制数据发愁。传统方法需要手动提取上百个特征&#xff0c;就像要求交警记住每辆车的发动机编号来管理交通。直到尝试用原始流量数据直接训练模型&#xff0c;才发现深度学习的…...

1979年11月3日晚上21-23点出生性格、运势和命运

在1979年11月3日晚上21 - 23点出生之人&#xff0c;其性格往往有着独特的烙印。这个时间段出生的人&#xff0c;性格多沉稳内敛&#xff0c;有着自己的思考方式和行事准则。他们通常具备较强的观察力&#xff0c;能敏锐地察觉到周围环境的细微变化&#xff0c;在与人交往中&…...

Qwen-Image-2512-Pixel-Art-LoRA 提示词工程进阶:掌握控制像素艺术风格与细节的秘诀

Qwen-Image-2512-Pixel-Art-LoRA 提示词工程进阶&#xff1a;掌握控制像素艺术风格与细节的秘诀 你是不是也遇到过这样的情况&#xff1a;用像素艺术模型生成图片&#xff0c;出来的效果要么像素块太大太粗糙&#xff0c;要么颜色花里胡哨不像复古游戏&#xff0c;要么就是画面…...

GD32单片机ADC实战:从传感器到上位机,搞定50kg压力采集全流程(附源码/原理图)

GD32单片机ADC实战&#xff1a;从传感器到上位机的50kg压力采集全流程解析 在嵌入式开发领域&#xff0c;压力采集系统是工业自动化、医疗设备和消费电子产品中的常见需求。本文将带你从零开始&#xff0c;使用GD32单片机的12位ADC模块&#xff0c;构建一个完整的50kg量程压力采…...

# 发散创新:基于Web Audio API的实时空间音频渲染实现在现代沉浸式音视频应用中,**空间音频(Spatial A

发散创新&#xff1a;基于Web Audio API的实时空间音频渲染实现 在现代沉浸式音视频应用中&#xff0c;空间音频&#xff08;Spatial Audio&#xff09; 已成为提升用户体验的核心技术之一。无论是VR/AR场景、游戏引擎还是远程协作工具&#xff0c;精准的声音定位能力直接决定了…...

Xinference-v1.17.1问题解决:常见部署错误排查,确保一次成功

Xinference-v1.17.1问题解决&#xff1a;常见部署错误排查&#xff0c;确保一次成功 1. 部署前的准备工作 1.1 系统环境检查 在部署Xinference-v1.17.1之前&#xff0c;确保您的系统满足以下最低要求&#xff1a; 操作系统&#xff1a;Ubuntu 20.04/22.04或CentOS 7/8&…...

C++零基础到工程实战(3.4.2):C++17 中 switch 初始化语句详解

目录 一、前言 二、switch 初始化语句是什么 三、GetPlay() 和 play.Status() 到底是什么意思 3.1 GetPlay() 是什么 3.2 play.Status() 是什么 四、完整示例解析&#xff1a; 4.1 示例&#xff1a; &#xff08;1&#xff09;代码 &#xff08;2&#xff09;变量名解…...