跳到主要内容

2 篇博文 含有标签「EasyExcel」

关于EasyExcel的相关内容

查看所有标签

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工具。


浏览量:加载中...

实用框架EasyExcel

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

背景EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。它有许许多多的应用场景 例如 数据导入:减轻录入工作量 ;数据导出:统计信息归档 ; 数据传输:异构系统之间数据传输等等

1、EasyExcel介绍

  • Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
  • EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
  • EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)

文档地址:https://easyexcel.opensource.alibaba.com

github地址:https://github.com/alibaba/easyexcel

2、EasyExcel写操作

2.1、创建项目

pom中引入xml相关依赖

<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
</dependencies>

当引入该依赖之后,会发现在项目的依赖文件中同时多出了poi的类库。也就是说,EasyExcel是基于poi来进行实现的,间接地引入了如下依赖:

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>

2.2、创建实体类

设置表头和添加的数据字段

@Data
public class Stu {
//设置表头名称
@ExcelProperty("学生编号")
private int sno;

//设置表头名称
@ExcelProperty("学生姓名")
private String sname;
}
  • @ExcelProperty:用于设置Excel表头,其中index用户表头的编号,从0开始;value为表头对应的内容。
  • @DateTimeFormat:用于日期的格式化

完成上述功能准备工作之后,我们就可以来生成一个Excel了。

2.3 、实现写操作

(1)创建方法循环设置要添加到Excel的数据

//循环设置要添加的数据,最终封装到list集合中
private static List<Stu> data() {
List<Stu> list = new ArrayList<Stu>();
for (int i = 0; i < 10; i++) {
Stu data = new Stu();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}

(2)实现最终的添加操作

public static void main(String[] args) throws Exception {
String fileName = "E:\\11.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, Stu.class).sheet("写入方法一").doWrite(data());
}

那么我们如何解析Excel呢?接着看....

3、EasyExcel读操作

3.1、创建实体类

@Data
public class Stu {

//设置表头名称
//设置列对应的属性
@ExcelProperty(value = "学生编号",index = 0)
private int sno;

//设置表头名称
//设置列对应的属性
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
}

首先创建一个监听器ExcelListener,集成EasyExcel提供AnalysisEventListener类:

3.2、创建读取操作监听器

public class ExcelListener extends AnalysisEventListener<Stu> {
//创建list集合封装最终的数据
List<Stu> list = new ArrayList<Stu>();
//一行一行去读取excle内容
@Override
public void invoke(Stu user, AnalysisContext analysisContext) {
System.out.println("***"+user);
list.add(user);
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("list = " + list);
}
}

在该监听器中,通过重写AnalysisEventListener的方法来获得解析的数据、表头信息,以及解析完毕之后执行的操作信息。

同样写Excel一样,通过EasyExcel类的静态方法来执行读操作:

3.3、调用方法实现读取

public static void main(String[] args) throws Exception {
String fileName = "E:\\11.xlsx";
// 这里 需要指定调用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, Stu.class, new ExcelListener()).sheet().doRead();
}

上面提到的@DateTimeFormat注解可转换日期格式,还有其他类似功能的注解和自定义转换器。

浏览量:加载中...