Java實(shí)現(xiàn)解析Excel復(fù)雜表頭
在Java中如果想解析復(fù)雜的Excel表頭,可以使用Apache POI庫(kù)。
廢話不多說(shuō),直接上源碼。前后端都有哦
后端
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工具類(復(fù)雜表頭解析)
* @Author: sh
* @Date: 2025/1/2 09:29
*/
public class ExcelComplexUtil {
/**
* 導(dǎo)入Excel文件,逐行讀取數(shù)據(jù),數(shù)據(jù)格式二維數(shù)組
* @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); // 獲取第一個(gè)工作表
// 獲取表頭行
Row headerRow = checkHeaderRow(sheet);
if (headerRow != null) {
//封裝表頭數(shù)據(jù)
warpHeaderData(headerRow, dataList);
} else {
throw new RuntimeException("Excel 文件中沒(méi)有找到表頭行,請(qǐng)修改表格");
}
// 讀取數(shù)據(jù)行
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
continue; // 跳過(guò)空行
}
//封裝行數(shù)據(jù)
warpRowData(headerRow, row, dataList);
}
}
// 將 List<String[]> 轉(zhuǎn)換為 String[][] 數(shù)組
String[][] result = new String[dataList.size()][];
for (int i = 0; i < dataList.size(); i++) {
result[i] = dataList.get(i);
}
return result; // 返回二維數(shù)組
}
/**
* 檢查表頭行,表頭行必須在前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;
}
/**
* 數(shù)據(jù)遍歷
* @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]; // 創(chuàng)建一維數(shù)組存儲(chǔ)表頭數(shù)據(jù)
for (int colIndex = 0; colIndex < columnCount; colIndex++) {
Cell cell = headerRow.getCell(lastCellNum - columnCount + colIndex);
String cellValue = (cell != null) ? cell.toString() : ""; // 處理空單元格
data[colIndex] = cellValue; // 將單元格值放入表頭數(shù)組中
}
dataList.add(data); // 將表頭數(shù)組添加到列表中
}
public void warpRowData(Row headerRow, Row row, List<String[]> dataList) {
int columnCount = headerRow.getPhysicalNumberOfCells();
short lastCellNum = headerRow.getLastCellNum();
String[] data = new String[columnCount]; // 創(chuàng)建一維數(shù)組存儲(chǔ)表頭數(shù)據(jù)
for (int colIndex = 0; colIndex < columnCount; colIndex++) {
Cell cell = row.getCell(lastCellNum - columnCount + colIndex);
String cellValue = (cell != null) ? cell.toString() : ""; // 處理空單元格
data[colIndex] = cellValue; // 將單元格值放入表頭數(shù)組中
}
dataList.add(data); // 將表頭數(shù)組添加到列表中
}
/**
* 獲取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); // 獲取第一個(gè)工作表
int numberOfMergedRegions = sheet.getNumMergedRegions(); // 獲取合并單元格的數(shù)量
List<MergedCell> mergedCellArray = new ArrayList<>();
for (int i = 0; i < numberOfMergedRegions; i++) {
MergedCell mergedCell = new MergedCell();
CellRangeAddress range = sheet.getMergedRegion(i); // 獲取合并單元格區(qū)域
mergedCell.setRange(range.formatAsString());
// 獲取合并單元格區(qū)域的起始單元格
int firstRow = range.getFirstRow();
int firstCol = range.getFirstColumn();
// 獲取合并單元格的內(nèi)容
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; // 如果該單元格在合并區(qū)內(nèi),返回 true
}
}
return false; // 如果不在任何合并區(qū)內(nèi),返回 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;
}
@Override
public 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)); // 以逗號(hào)為分隔符打印每一行
// }
// List<MergedCell> mergedCells = checkMergedCells(filePath, 0);
// for (MergedCell row : mergedCells) {
// System.out.println(row); // 以逗號(hào)為分隔符打印每一行
// }
//
// } 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; /* 設(shè)置表格寬度 */
}
td {
border: 1px solid black;
padding: 0; /* 清除內(nèi)邊距 */
text-align: center;
vertical-align: middle; /* 垂直居中 */
height: 50px; /* 設(shè)置每個(gè)單元格的高度 */
}
.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">
<td
v-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: [
["監(jiān)督員地區(qū)", "總數(shù)", "有效", "", "有效政治類", ""],
["平谷區(qū)監(jiān)督員", "244", "", "197", "28", "169"],
["", "244", "", "197", "28", ""],
["數(shù)據(jù) 16", "數(shù)據(jù) 17", "數(shù)據(jù) 18", "數(shù)據(jù) 19", "數(shù)據(jù) 20"],
["數(shù)據(jù) 21", "數(shù)據(jù) 22", "數(shù)據(jù) 23", "數(shù)據(jù) 24", "數(shù)據(jù) 25"],
["", "", "", "", ""]
],
occupiedCells: []
};
},
computed: {
// 生成完整表格數(shù)據(jù)
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; // 標(biāo)記占用
}
}
}
});
// 填充普通單元格的數(shù)據(jù)
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>到此這篇關(guān)于Java實(shí)現(xiàn)解析Excel復(fù)雜表頭的文章就介紹到這了,更多相關(guān)Java解析Excel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot處理form-data表單接收對(duì)象數(shù)組的方法
form-data則是一種更加靈活的編碼方式,它可以處理二進(jìn)制數(shù)據(jù)(如圖片、文件等)以及文本數(shù)據(jù),這篇文章主要介紹了SpringBoot處理form-data表單接收對(duì)象數(shù)組,需要的朋友可以參考下2023-11-11
Java?根據(jù)XPATH批量替換XML節(jié)點(diǎn)中的值
這篇文章主要介紹了Java根據(jù)XPATH批量替換XML節(jié)點(diǎn)中的值,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
使用Jackson進(jìn)行JSON生成與解析的新手指南
這篇文章主要為大家詳細(xì)介紹了如何使用Jackson進(jìn)行JSON生成與解析處理,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
Java Spring的數(shù)據(jù)庫(kù)開(kāi)發(fā)詳解
這篇文章主要介紹了Spring的數(shù)據(jù)庫(kù)開(kāi)發(fā),主要圍繞SpringJDBC和Spring Jdbc Template兩個(gè)技術(shù)來(lái)講解,文中有詳細(xì)的代碼示例,需要的小伙伴可以參考一下2023-04-04
java高并發(fā)的volatile與Java內(nèi)存模型詳解
這篇文章主要介紹了java高并發(fā)的volatile與Java內(nèi)存模型,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-10-10

