目录
一 EasyExcel简介
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知(AnalysisEventListener)
二 导出场景
为了方便大家理解,拦截器实现的都是ReadListener,其实用AnalysisEventListener更高效.大家在实际应用中可以换成AnalysisEventListener,实现改成继承就可以,可以参考普通读的监听器EasyExcelListener.
pom依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
代码导入包
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.minghe.jiaozhu.listener.*;
import com.minghe.jiaozhu.model.dto.ImportExcel;
import com.minghe.jiaozhu.service.ImportService;
import org.springframework.stereotype.Service;
1.普通读
代码
public void importExcel(String path) {
EasyExcelListener listener = new EasyExcelListener<>();
//headRowNumber表头所在的行数
EasyExcel.read(path, ImportExcel.class, listener).headRowNumber(3).sheet().doReadSync();
List<ImportExcel> data = listener.getData();
for (ImportExcel datum : data) {
System.out.println(datum);
}
}
第二种实现方式
public void importExcel(String path) {
List<ImportExcel> dataList = EasyExcel.read(path)
.sheet("sheetName")
.headRowNumber(3)
.head(ImportExcel.class)
.doReadSync();
for (ImportExcel importExcel : dataList) {
System.out.println(importExcel);
}
}
这种方式好处是无需创建监听器
.headRowNumber(3) 这句代码意思是指定表头,将你传入的class和第三行匹配,第三行之后的都作为数据来读取.
监听器
@Data
public class EasyExcelListener<T> extends AnalysisEventListener<T> {
private List<T> data = new ArrayList<>();
@Override
public void invoke(T t, AnalysisContext analysisContext) {
data.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
2.读取多sheet
public void importSheetsExcel(String path) {
EasyExcelListener listener = new EasyExcelListener();
List<Class> clazzs = new ArrayList<>();
clazzs.add(ImportExcel.class);
clazzs.add(ImportExcel2.class);
List<ReadSheet> readSheets = EasyExcel.read(path, listener).headRowNumber(1).build().excelExecutor().sheetList();
List<List<Object>> dataList = new ArrayList<>();
for (int i = 0; i < readSheets.size(); i++) {
dataList.add(EasyExcel.read(path)
.sheet(i)
//这样就要求所有sheet页的表头的所在行数是一致的
.headRowNumber(1)
.head(clazzs.get(i))
.doReadSync());
}
for (List<Object> objects : dataList) {
for (Object object : objects) {
System.out.println(object);
}
}
}
用这种方式读取多sheet要求所有表头所在行是一样的,第一个sheet表头在第一行的话,其他所有sheet的表头也都得在第一行.
还有一张方式,这种方式可以单独设定表头,但这种方式会把每个sheet的表头都读出来.真的需要这种方式的话,就把data喝data1的.get(0)去掉就行
/* @Override
public void importSheetsExcel(String path) {
EasyExcelListener listener = new EasyExcelListener();
EasyExcelListener listener2 = new EasyExcelListener();
ExcelReader excelReader = EasyExcel.read(path, ImportExcel.class, listener).headRowNumber(3).build();
ExcelReader excelReader1 = EasyExcel.read(path, ImportExcel2.class, listener2).headRowNumber(1).build();
List<ReadSheet> readSheets = excelReader.excelExecutor().sheetList();
for (ReadSheet readSheet : readSheets) {
String sheetName = readSheet.getSheetName();
ReadSheet build = EasyExcel.readSheet(sheetName).build();
excelReader.read(build);
excelReader1.read(build);
}
excelReader.finish();
excelReader1.finish();
List<ImportExcel> data = listener.getData();
for (ImportExcel importExcel : data) {
System.out.println("1"+importExcel);
}
List<ImportExcel2> data1 = listener2.getData();
for (ImportExcel2 importExcel2 : data1) {
System.out.println("2"+importExcel2);
}
}*/
监听器 同上
3.读取表头
@Override
public void importExcelHead(String path) {
EasyExcelHeadListener listener = new EasyExcelHeadListener<>();
//headRowNumber表头所在的行数
EasyExcel.read(path, ImportExcel.class, listener).headRowNumber(1).sheet().doReadSync();
//数据
List<ImportExcel> data = listener.getData();
for (ImportExcel datum : data) {
System.out.println(datum);
}
//表头
//应该是headRowNumber设定的行数上方都算表头,都能读出来
List<Map<Integer, String>> headerMapsList = listener.getHeaderMapsList();
for (Map<Integer, String> integerStringMap : headerMapsList) {
System.out.println(integerStringMap);
}
}
监听器
@Data
public class EasyExcelHeadListener<T> implements ReadListener<T> {
private List<T> data = new ArrayList<>();
private List<Map<Integer, String>> headerMapsList = new ArrayList<>();
@Override
public void invoke(T t, AnalysisContext analysisContext) {
data.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
Map<Integer, String> integerStringMap = ConverterUtils.convertToStringMap(headMap, context);
headerMapsList.add(integerStringMap);
}
4.读取额外信息 批注 超链接 合并的单元格
代码
public void importExcelExtra(String path) {
//headRowNumber表头所在的行数
EasyExcel.read(path, ImportExcel.class, new EasyExcelExtraListener()).headRowNumber(3)
// 需要读取批注 默认不读取
.extraRead(CellExtraTypeEnum.COMMENT)
// 需要读取超链接 默认不读取
.extraRead(CellExtraTypeEnum.HYPERLINK)
// 需要读取合并单元格信息 默认不读取
.extraRead(CellExtraTypeEnum.MERGE)
.sheet()
.doReadSync();
}
监听器
@Data
@Slf4j
public class EasyExcelExtraListener<T> implements ReadListener<T> {
private List<T> data = new ArrayList<>();
@Override
public void invoke(T t, AnalysisContext analysisContext) {
data.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
@SneakyThrows
@Override
public void extra(CellExtra extra, AnalysisContext context) {
log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
switch (extra.getType()) {
case COMMENT:
log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(),
extra.getText());
break;
case HYPERLINK:
if ("Sheet1!A1".equals(extra.getText())) {
log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(),
extra.getColumnIndex(), extra.getText());
} else if ("Sheet2!A1".equals(extra.getText())) {
log.info(
"额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{},"
+ "内容是:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex(), extra.getText());
}
break;
case MERGE:
log.info(
"额外信息是合并单元格,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}"
+ "内容是:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex(), extra.getText());
break;
default:
}
}
}
5.读取特定某行
代码
public void importExcelReadRow(String path) {
//读取第一行第一列
EasyExcelCellListener easyExcelCellListener = new EasyExcelCellListener(1, 1);
EasyExcel.read(path, easyExcelCellListener)
//headRowNumber(0)表头所在行要写0,不写的话默认第一行是表头
.headRowNumber(0)
.sheet()
.doReadSync();
String cellValue = easyExcelCellListener.getCellValue();
System.out.println(cellValue);
}
监听器
public class EasyExcelCellListener implements ReadListener<Map<Integer,String>> {
private String cellValue;
private int targetRow;
private int targetColumn;
private boolean isCellRead = false;
public EasyExcelCellListener(int targetRow, int targetColumn) {
this.targetRow = targetRow;
this.targetColumn = targetColumn;
}
@Override
//invoke方法的参数类型跟随实现的ReadListener的类型
public void invoke(Map<Integer,String> map, AnalysisContext analysisContext) {
if(isCellRead){
return;
}
int currentRow = analysisContext.readRowHolder().getRowIndex()+1;
if(currentRow == targetRow ){
cellValue = map.get(targetColumn-1);
isCellRead = true;
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
public String getCellValue() {
return cellValue;
}
}
6.读取时异常处理
代码
public void importExcelException(String path) {
//headRowNumber表头所在的行数
EasyExcel.read(path, ImportExcel.class, new EasyExcelExceptionListener())
.headRowNumber(3)
.sheet()
.doReadSync();
}
监听器
@Data
@Slf4j
public class EasyExcelExceptionListener<T> implements ReadListener<T> {
private List<T> data = new ArrayList<>();
@Override
public void invoke(T t, AnalysisContext analysisContext) {
data.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
@Override
//只要补抛出异常就能继续解析
public void onException(Exception exception, AnalysisContext context) throws Exception {
log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
//这里只是转换异常,可以捕获其他异常
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
}
}
}
git仓库:导入导出: 导入导出的实例
多场景easyExcel导出excel文件(一):多场景easyExcel导出excel文件(一)-CSDN博客
根据模板填充excel:多场景easyExcel根据模板填充excel文件(三)-CSDN博客