跳到主要内容

EasyExcel中的数据第一行获取问题及解决方案详解

· 阅读需 6 分钟
季冠臣
后端研发工程师

EasyExcel中的数据第一行获取问题及解决方案详解

在Java开发中,处理Excel文件是一个常见的需求。EasyExcel作为一个流行的Excel操作库,提供了方便而高效的API来读写Excel文件。然而,有时会遇到数据第一行被误读为表头的问题,特别是在Excel文件的第一行不是标准表头而是实际数据时,这一问题显得尤为突出。本文将详细讨论这一问题的根本原因,并提供一种有效的解决方案。

1. EasyExcel简介

EasyExcel是阿里巴巴开源的一款Java操作Excel的工具库,它提供了强大的功能,支持大数据量的读写操作,并且提供了丰富的样式和格式处理功能,适用于各种场景下的Excel文件处理需求。你可以访问 EasyExcel 的官方 GitHub 页面获取更多的资料和下载: https://github.com/alibaba/easyexcel

2. 问题描述

在使用EasyExcel读取Excel文件时,经常会出现第一行数据被错误地识别为表头的情况。这一问题的根本原因在于EasyExcel在某些情况下无法正确识别Excel文件中数据行和表头行的区分,特别是当Excel文件结构比较复杂或者存在特定格式时,EasyExcel的默认解析逻辑可能会出现偏差。

3. 解决方案详解

为了解决数据第一行获取问题,我们可以采取以下步骤来调整和优化EasyExcel的读取操作,确保能够正确获取实际数据行而非表头行:

方案1:手动指定数据起始行

在读取Excel文件时,手动指定数据的起始行,而不依赖EasyExcel的自动识别。这可以通过设置headRowNumber来实现,明确告知EasyExcel从第几行开始读取数据。

ExcelReaderBuilder readBuilder = EasyExcel.read(inputStream, ExcelData.class, new ExcelDataListener())
.sheet().headRowNumber(2) // 指定从第3行开始读取数据
.doRead();

在上述代码中,通过headRowNumber(2)指定从第3行开始读取数据,避免将第一行误读为表头。

数据处理逻辑中排除表头行

在实际数据处理逻辑中,可以通过逻辑判断排除表头行,确保只处理实际的数据行。例如,在invoke方法中可以添加逻辑判断:

@Override 
public void invoke(ExcelData data, AnalysisContext context) {
if (context.readRowHolder().getRowIndex() > 0) { // 跳过表头行
// 处理实际数据逻辑
}
}

通过context.readRowHolder().getRowIndex() > 0判断当前行索引大于0时才处理数据,跳过表头行的处理。

使用后处理器进行二次处理

EasyExcel提供了后处理器(Handler)机制,在数据读取完成后可以进行二次处理。可以在doAfterAllAnalysed方法中对数据进行进一步处理或过滤,确保最终数据的准确性和完整性。

@Override 
public void doAfterAllAnalysed(AnalysisContext context) {
// 数据处理完成后的逻辑
processData(dataList);
}

在上述代码中,可以在doAfterAllAnalysed方法中调用processData方法,对数据进行进一步的处理或者存储操作。

示例代码

以下是一个完整的示例代码,展示了如何使用EasyExcel读取Excel文件并处理数据,同时避免数据第一行被误读为表头的问题:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class ExcelReaderExample {

public static void main(String[] args) {
String fileName = "path/to/your/excel/file.xlsx";
InputStream inputStream = null;
try {
inputStream = new FileInputStream(fileName);
ExcelReaderBuilder readBuilder = EasyExcel.read(inputStream, ExcelData.class, new ExcelDataListener());
readBuilder.sheet().headRowNumber(2); // 指定从第3行开始读取数据
readBuilder.doRead();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

public static class ExcelDataListener extends AnalysisEventListener<ExcelData> {

private List<ExcelData> dataList = new ArrayList<>();

@Override
public void invoke(ExcelData data, AnalysisContext context) {
if (context.readRowHolder().getRowIndex() > 0) { // 跳过表头行
// 处理实际数据逻辑
dataList.add(data);
}
}

@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 数据处理完成后的逻辑
processData(dataList);
}

private void processData(List<ExcelData> dataList) {
// 处理数据的具体逻辑
for (ExcelData data : dataList) {
System.out.println(data.toString());
}
}
}

public static class ExcelData {
// Excel中的数据字段对应的Java属性
private String column1;
private String column2;

// 省略getter和setter方法
}
}
方案2:第一行数据读在表头单独处理
@Override
@SneakyThrows
public byte[] exportExcel(List<String> sheet1Ids, List<String> sheet2Ids, List<String> sheet3Ids, String startDate, String endDate) {
byte[] result;

// 读取模版
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
InputStream inputStream = templateFileService.getTemplateInputStreamByPath(dataComparisonFileResource.getPath())) {
ExcelWriter excelWriter = EasyExcel.write(outputStream)
.withTemplate(inputStream)
.autoCloseStream(true)
.registerWriteHandler(new CustomSheet1CellWriteHandler())
.build();

// 写入表1 数据
List<LfDispatchDataComparisonGridOperationVo> sheet1Data = getGridOperationData(sheet1Ids, startDate, endDate);
this.writerSheet1(excelWriter, sheet1Data);

// 写入表2 数据
List<LfDispatchDataComparisonGwEnergyVo> sheet2Data = getGwEnergyData(sheet2Ids, startDate, endDate);
this.writerSheet2(excelWriter, sheet2Data);

// 写入表3 数据
List<LfDispatchDataComparisonNewEnergyOperationVo> sheet3Data = getNewEnergyOperationData(sheet3Ids, startDate, endDate);
this.writerSheet3(excelWriter, sheet3Data);

// 生成excel
excelWriter.finish();

// 确保所有内容都写入输出流中
result = outputStream.toByteArray();
}

return result;
}

/**
* 自定义sheet1的cell样式 用于标记 负荷占比、电量占比 大于1的单元格
*/
public static class CustomSheet1CellWriteHandler implements WorkbookWriteHandler, CellWriteHandler {

/**
* 处理第一行数据 (easyExcel工具的bug 会将第一行数据读在表头)
*
* @param context 上下文
*/
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
if (context.getRowIndex() == 2 && (context.getColumnIndex() == 14 || context.getColumnIndex() == 15)) {
// 处理第第一个sheet
Sheet sheet = context.getRow().getSheet().getWorkbook().getSheetAt(0);
Row row = sheet.getRow(2);

Cell cell14 = row.getCell(14);
Cell cell15 = row.getCell(15);

// 应用红色样式
applyRedStyleToCells(cell14, cell15);
}
}

/**
* 处理除了第一行以外的其他数据行
*
* @param context 上下文
*/
@Override
public void afterWorkbookDispose(WorkbookWriteHandlerContext context) {
Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
// 只处理 sheet1
Sheet sheet = workbook.getSheetAt(0);

applyRedStyleToSheet(sheet);
}

/**
* 按列标记红色
*
* @param sheet 工作表
*/
private void applyRedStyleToSheet(Sheet sheet) {
for (Row row : sheet) {
applyRedStyleToCells(row.getCell(14), row.getCell(15));
}
}

/**
* 对指定的单元格应用红色字体样式
*
* @param cells 需要应用红色样式的单元格
*/
private void applyRedStyleToCells(Cell... cells) {
for (Cell cell : cells) {
if (cell != null) {
Workbook workbook = cell.getSheet().getWorkbook();
CellStyle redStyle = workbook.createCellStyle();
Font redFont = workbook.createFont();
redFont.setColor(IndexedColors.RED.getIndex());
redStyle.setFont(redFont);
cell.setCellStyle(redStyle);
}
}
}
}

5. 结论

我总结:建议不再使用EasyExcel工具。


浏览量:加载中...