問題背景
需求:俺現網班級作為維度,批量導出每個班級學員的數據,excel的個數在1k左右,每一張表的人數在90左右。導出總耗時在10小時左右。
代碼編寫完成并導出現網數據后,發現導出的標題錯了。
解決方案
1.通過修改代碼,重新導出。(耗時在10h)
2.通過java 代碼實現excel標簽替換。(耗時在10分鐘)
代碼實現
依賴
implementation "org.apache.poi:poi:5.2.3"implementation "org.apache.poi:poi-ooxml:5.2.3"
代碼?
其中當文件中只有舊的標簽且其他數據不存在時,會直接報錯,需要我們手動處理即可。
template為我們新模板的樣式文件,sourseDir為舊excel的文件夾。outputDir為新文件的生成位置。
根據樣式的實際行數修改readTemplateData中的循環行數。removeRows方法中設置舊excel中標簽的起止行數。(索引從0開始)
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ExecelUtios {public static void main(String[] args) {String templatePath = "C:\\Users\\fjhb\\Desktop\\test111\\template.xlsx";String sourceDir = "C:\\Users\\fjhb\\Desktop\\教育學院\\4考勤\\2023";String outputDir = "C:\\Users\\fjhb\\Desktop\\教育學院\\4考勤最總\\2023\\";try {// 讀取模板數據(內容和樣式)TemplateData templateData = readTemplateData(templatePath);List<String> errorFileName = new ArrayList<>();File dir = new File(sourceDir);File[] files = dir.listFiles((d, name) ->name.toLowerCase().endsWith(".xls") || name.toLowerCase().endsWith(".xlsx"));if (files == null || files.length == 0) {System.out.println("目錄中沒有Excel文件");return;}System.out.println("開始處理 " + files.length + " 個文件...");for (File file : files) {try {processFile(file, templateData, outputDir);System.out.println("? 已處理: " + file.getName());} catch (Exception e) {System.err.println("? 處理失敗: " + file.getName() + " - " + e.getMessage());errorFileName.add(file.getName());}}System.out.println("處理完成! 成功處理 " + files.length + " 個文件");if(!errorFileName.isEmpty()) {System.out.println("執行錯誤,需要手動處理的文件為下(因為文件沒有內容只有標題):");for (String s : errorFileName) {System.err.println(s);}}} catch (Exception e) {e.printStackTrace();}}// 讀取模板數據(包含樣式、內容和合并單元格)private static TemplateData readTemplateData(String templatePath) throws IOException {try (InputStream is = new FileInputStream(templatePath);Workbook templateWorkbook = WorkbookFactory.create(is)) {Sheet sheet = templateWorkbook.getSheetAt(0);List<RowData> rows = new ArrayList<>();List<CellRangeAddress> mergedRegions = new ArrayList<>();// 讀取前三行for (int i = 0; i < 3 && i <= sheet.getLastRowNum(); i++) {Row row = sheet.getRow(i);if (row != null) {rows.add(new RowData(row, templateWorkbook));}}// 讀取前三行的合并單元格區域for (int i = 0; i < sheet.getNumMergedRegions(); i++) {CellRangeAddress mergedRegion = sheet.getMergedRegion(i);if (mergedRegion.getLastRow() < 3) { // 只處理前三行的合并mergedRegions.add(mergedRegion);}}return new TemplateData(rows, mergedRegions, templateWorkbook);}}// 處理單個文件private static void processFile(File file, TemplateData templateData, String outputDir)throws IOException {try (InputStream is = new FileInputStream(file);Workbook workbook = WorkbookFactory.create(is)) {Sheet sheet = workbook.getSheetAt(0);// 1. 刪除原有的合并區域(前三行)removeMergedRegionsInRange(sheet, 0, 2);// 2. 刪除原有的前兩行removeRows(sheet, 0, 1);// 3. 插入模板行(帶樣式)insertTemplateRows(sheet, templateData, workbook);// 4. 確保輸出目錄存在File outDir = new File(outputDir);if (!outDir.exists()) outDir.mkdirs();// 5. 保存文件String outputPath = outputDir + File.separator + file.getName();try (OutputStream os = new FileOutputStream(outputPath)) {workbook.write(os);}}}// 刪除指定行范圍內的合并區域private static void removeMergedRegionsInRange(Sheet sheet, int startRow, int endRow) {for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {CellRangeAddress mergedRegion = sheet.getMergedRegion(i);if (mergedRegion.getFirstRow() >= startRow && mergedRegion.getLastRow() <= endRow) {sheet.removeMergedRegion(i);}}}// 刪除指定行范圍private static void removeRows(Sheet sheet, int startRow, int endRow) {// 刪除行內容for (int i = startRow; i <= endRow; i++) {Row row = sheet.getRow(i);if (row != null) {sheet.removeRow(row);}}// 移動行if (endRow < sheet.getLastRowNum()) {sheet.shiftRows(endRow + 1, sheet.getLastRowNum(), -(endRow - startRow + 1));}}// 插入模板行(帶樣式)private static void insertTemplateRows(Sheet sheet, TemplateData templateData, Workbook targetWorkbook) {if (templateData.rows.isEmpty()) return;// 移動現有行sheet.shiftRows(0, sheet.getLastRowNum(), templateData.rows.size(), true, true);// 創建新行并應用模板for (int i = 0; i < templateData.rows.size(); i++) {Row newRow = sheet.createRow(i);templateData.rows.get(i).applyTo(newRow, targetWorkbook, templateData.sourceWorkbook);}// 添加合并區域for (CellRangeAddress mergedRegion : templateData.mergedRegions) {sheet.addMergedRegion(mergedRegion);}}// 模板數據容器static class TemplateData {final List<RowData> rows;final List<CellRangeAddress> mergedRegions;final Workbook sourceWorkbook;public TemplateData(List<RowData> rows, List<CellRangeAddress> mergedRegions, Workbook sourceWorkbook) {this.rows = rows;this.mergedRegions = mergedRegions;this.sourceWorkbook = sourceWorkbook;}}// 行數據容器static class RowData {private final List<CellData> cells = new ArrayList<>();public RowData(Row sourceRow, Workbook sourceWorkbook) {if (sourceRow != null) {for (Cell cell : sourceRow) {cells.add(new CellData(cell, sourceWorkbook));}}}public void applyTo(Row targetRow, Workbook targetWorkbook, Workbook sourceWorkbook) {for (CellData cellData : cells) {cellData.applyTo(targetRow, targetWorkbook, sourceWorkbook);}}}// 單元格數據容器(包含樣式)static class CellData {private final int columnIndex;private final CellStyle sourceStyle;private final Object value;private final CellType cellType;private final Workbook sourceWorkbook;public CellData(Cell sourceCell, Workbook sourceWorkbook) {this.columnIndex = sourceCell.getColumnIndex();this.sourceStyle = sourceCell.getCellStyle();this.sourceWorkbook = sourceWorkbook;this.cellType = sourceCell.getCellType();switch (cellType) {case STRING:value = sourceCell.getStringCellValue();break;case NUMERIC:value = sourceCell.getNumericCellValue();break;case BOOLEAN:value = sourceCell.getBooleanCellValue();break;case FORMULA:value = sourceCell.getCellFormula();break;case BLANK:value = "";break;default:value = null;}}public void applyTo(Row targetRow, Workbook targetWorkbook, Workbook sourceWorkbook) {Cell newCell = targetRow.createCell(columnIndex);// 復制單元格值setCellValue(newCell, value, cellType);// 復制單元格樣式(深度復制)if (sourceStyle != null) {CellStyle newStyle = targetWorkbook.createCellStyle();copyCellStyleDeep(newStyle, sourceStyle, targetWorkbook, sourceWorkbook);newCell.setCellStyle(newStyle);}}private void setCellValue(Cell cell, Object value, CellType cellType) {if (value == null) return;switch (cellType) {case STRING:cell.setCellValue((String) value);break;case NUMERIC:cell.setCellValue((Double) value);break;case BOOLEAN:cell.setCellValue((Boolean) value);break;case FORMULA:cell.setCellFormula((String) value);break;case BLANK:cell.setBlank();break;default:}}// 深度復制單元格樣式(支持.xls和.xlsx)private void copyCellStyleDeep(CellStyle newStyle, CellStyle sourceStyle,Workbook targetWorkbook, Workbook sourceWorkbook) {// 復制基本樣式屬性newStyle.setAlignment(sourceStyle.getAlignment());newStyle.setVerticalAlignment(sourceStyle.getVerticalAlignment());newStyle.setBorderTop(sourceStyle.getBorderTop());newStyle.setBorderBottom(sourceStyle.getBorderBottom());newStyle.setBorderLeft(sourceStyle.getBorderLeft());newStyle.setBorderRight(sourceStyle.getBorderRight());newStyle.setTopBorderColor(sourceStyle.getTopBorderColor());newStyle.setBottomBorderColor(sourceStyle.getBottomBorderColor());newStyle.setLeftBorderColor(sourceStyle.getLeftBorderColor());newStyle.setRightBorderColor(sourceStyle.getRightBorderColor());newStyle.setFillPattern(sourceStyle.getFillPattern());// 復制背景色if (sourceStyle.getFillBackgroundColor() > 0) {newStyle.setFillBackgroundColor(sourceStyle.getFillBackgroundColor());}// 復制前景色if (sourceStyle.getFillForegroundColor() > 0) {newStyle.setFillForegroundColor(sourceStyle.getFillForegroundColor());}// 復制其他屬性newStyle.setDataFormat(sourceStyle.getDataFormat());newStyle.setWrapText(sourceStyle.getWrapText());newStyle.setIndention(sourceStyle.getIndention());newStyle.setRotation(sourceStyle.getRotation());newStyle.setHidden(sourceStyle.getHidden());newStyle.setLocked(sourceStyle.getLocked());newStyle.setShrinkToFit(sourceStyle.getShrinkToFit());// 復制字體Font sourceFont = sourceWorkbook.getFontAt(sourceStyle.getFontIndex());Font newFont = targetWorkbook.createFont();copyFontDeep(newFont, sourceFont, targetWorkbook, sourceWorkbook);newStyle.setFont(newFont);}// 深度復制字體樣式private void copyFontDeep(Font newFont, Font sourceFont,Workbook targetWorkbook, Workbook sourceWorkbook) {newFont.setBold(sourceFont.getBold());newFont.setColor(sourceFont.getColor());newFont.setFontHeight(sourceFont.getFontHeight());newFont.setFontHeightInPoints(sourceFont.getFontHeightInPoints());newFont.setFontName(sourceFont.getFontName());newFont.setItalic(sourceFont.getItalic());newFont.setStrikeout(sourceFont.getStrikeout());newFont.setTypeOffset(sourceFont.getTypeOffset());newFont.setUnderline(sourceFont.getUnderline());newFont.setCharSet(sourceFont.getCharSet());}}}
批量執行即可。
執行效果為下: