EasyExcel是Alibaba开源的一个Java处理Excel的工具。
官网解读:快速、简洁、解决大文件内存溢出的java处理Excel工具
快速
快速的读取
excel
中的数据。简洁
映射
excel
和实体类,让代码变的更加简洁。大文件
在读写大文件的时候使用磁盘做缓存,更加的节约内存。
官网地址:/
感兴趣可自己琢磨,该工具简单易上手,且性能相对比较高。
本文主要处理的问题是该工具读取Excel空数据行的问题。
首先解释为什么会产生空数据行:简单解释就是你在Excel中设置了单元的样式,却没有给单元格设值。因此,该工具在读取数据时便没有判断这一步,直接读取到整行数据均为null。
理解了核心问题后,要解决这个问题,实现思路也不难。
莫非就是把这种空数据行过滤即可。
本文是基于批处理监听器实现数据读取的,自定义集成该监听器(),实现自己的逻辑即可解决问题。
下面是自定义监听器
package ;
import ;
import ;
import ;
import ;
import .slf4j.Slf4j;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* 自定义分批Excel数据读取监听器,解决官方无法移除空的Excel行问题
*
* @param <T>
* @author geng
*/
@Slf4j
public class BatchPageReadListener<T> extends PageReadListener<T> {
/**
* Temporary storage of data
*/
private List<T> cachedDataList = (BATCH_COUNT);
/**
* consumer
*/
private final Consumer<List<T>> consumer;
public BatchPageReadListener(Consumer<List<T>> consumer) {
super(consumer);
= consumer;
}
@Override
public void invoke(T data, AnalysisContext context) {
// 如果一行Excel数据均为空值,则不装载该行数据
if (isLineNullValue(data)) {
return;
}
(data);
if (() >= BATCH_COUNT) {
(cachedDataList);
cachedDataList = (BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if ((cachedDataList)) {
return;
}
(cachedDataList);
}
/**
* 判断整行单元格数据是否均为空
*/
private boolean isLineNullValue(T data) {
if (data instanceof String) {
return (data);
}
try {
List<Field> fields = (().getDeclaredFields())
.filter(f -> ())
.collect(());
List<Boolean> lineNullList = new ArrayList<>(());
for (Field field : fields) {
(true);
Object value = (data);
if ((value)) {
();
} else {
();
}
}
return ().allMatch(::equals);
} catch (Exception e) {
("读取数据行[{}]解析失败: {}", data, ());
}
return true;
}
}
下面是我对EasyExcel封装的工具类
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .slf4j.Slf4j;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .*;
/**
* EasyExcel 帮助类
*/
@Slf4j
public class EasyExcelHelper {
/**
* 读取Excel文件
*
* @param stream 文件流
* @param entityClass 读取转换的Java对象类型
* @param <T>
* @return
*/
public static <T> List<T> doReadExcelData(InputStream stream, Class<T> entityClass) {
List<T> data = new LinkedList<>();
(stream, entityClass, new PageReadListener<T>(data::addAll)).sheet().doRead();
return data;
}
/**
* 读取Excel文件
*
* @param stream 文件流
* @param entityClass 读取转换的Java对象类型
* @param comparator 排序比较器
* @param <T>
* @return
*/
public static <T> List<T> doReadExcelData(InputStream stream, Class<T> entityClass, Comparator<T> comparator) {
List<T> data = new LinkedList<>();
(stream, entityClass, new BatchPageReadListener<T>(list -> {
if (comparator != null) {
(comparator);
}
(list);
})).sheet().doRead();
return data;
}
/**
* 读取Excel文件
*
* @param file MultipartFile文件
* @param entityClass 读取转换的Java对象类型
*/
@SneakyThrows
public static <T> List<T> doReadExcelData(MultipartFile file, Class<T> entityClass) {
return doReadExcelData((), entityClass);
}
/**
* 读取Excel文件
*
* @param file File文件
* @param entityClass 读取转换的Java对象类型
*/
@SneakyThrows
public static <T> List<T> doReadExcelData(File file, Class<T> entityClass) {
List<T> data = new LinkedList<>();
(file, entityClass, new BatchPageReadListener<T>(data::addAll)).sheet().doRead();
return data;
}
/**
* Excel数据浏览器下载
*
* @param pathName 下载文件的完整路径名称
* @param data 需下载数据
* @param entityClazz 下载数据类型模板
*/
@SneakyThrows
public static <T> void downloadExcel(String pathName, List<T> data, Class<T> entityClazz) {
try {
// 构建Excel表头及数据体
ExcelWriterSheetBuilder builder = (pathName)
.autoCloseStream(true)
.sheet("sheet1");
doWriteWithDynamicColumns(builder, entityClazz, data);
} catch (Exception e) {
("写文件错误:{}", ());
throw new PlatformException("Excel下载数据错误");
}
}
/**
* Excel数据浏览器下载
* <p>
* 前端下载js代码示例:
* <pre>
* function downloadFile(bytes, fileName) {
* const blob = new Blob([bytes], {type: 'application/'});
* if () { // 兼容IE10
* (blob, fileName)
* } else {
* const url = (blob);
* const a = ('a');
* = url;
* = fileName;
* ();
* (url);
* }
* }
* </pre>
*
* @param excelFileName 下载文件名称
* @param response 响应容器
* @param data 需下载数据
* @param entityClazz 下载数据Bean实体类型,苏醒必须使用注解<code>@ExcelProperty</code>中value指定写出列的表头吗,名称
*/
public static <T> void downloadExcelToResponse(HttpServletResponse response, String excelFileName, List<T> data, Class<T> entityClazz) {
if ((data)) {
("写文件错误:{}", "暂无可下载的数据");
writeErrMsg(response, "暂无可下载的数据");
return;
}
try {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
("application/");
("utf-8");
if ((".xlsx") || (".xls") ||
(".XLSX") || (".XLS")) {
excelFileName = (0, ("."));
}
// 这里可以防止中文乱码 当然和easy excel没有关系
String urlFileName = (excelFileName, "UTF-8").replaceAll("\\+", "%20");
("Content-disposition", "attachment;filename*=utf-8''" + urlFileName + ".xlsx");
("excel-file-name", urlFileName + ".xlsx");
// 构建Excel表头及数据体
ExcelWriterSheetBuilder builder = (())
.excelType()
.autoCloseStream(true)
.sheet("sheet1");
doWriteWithDynamicColumns(builder, entityClazz, data);
} catch (Exception e) {
("写文件错误:{}", ());
writeErrMsg(response, ());
}
}
/**
* EasyExcel支持动态列写数据
*
* @param builder 指定输出方式和样式
* @param entityClazz 实体的Class对象
* @param data Excel行数据
*/
public static <T> void doWriteWithDynamicColumns(ExcelWriterSheetBuilder builder, Class<T> entityClazz, List<T> data) {
List<HeadVO> customizeHeads = new ArrayList<>();
Field[] fieldArray = ();
// 获取类的注解
for (Field field : fieldArray) {
// 忽略导出属性
if (()) {
continue;
}
if (()) {
ExcelProperty excelProperty = ();
List<String> head = (());
int index = ();
int order = ();
HeadVO headVO = ().headTitle(head).index(index).order(order).field(()).build();
(headVO);
}
}
// 表头排序
(customizeHeads);
// 处理表头
List<List<String>> heads = new ArrayList<>();
List<String> fields = new ArrayList<>();
for (int i = 0; i <= () - 1; i++) {
((i).getHeadTitle());
((i).getField());
}
// 处理数据
List<List<Object>> objs = new ArrayList<>();
List<Map<String, ?>> maps = (data);
(map -> {
List<Object> obj = new ArrayList<>();
for (String field : fields) {
((field));
}
(obj);
});
(heads).doWrite(objs);
}
@SneakyThrows
private static void writeErrMsg(HttpServletResponse response, String errMsg) {
// 重置response
();
("application/json");
("utf-8");
().println(((errMsg)));
}
}
package ;
import ;
import ;
import ;
/**
* EasyExcel 表头信息VO类
*/
@Builder
@Data
public class HeadVO implements Comparable<HeadVO> {
/**
* Excel表头名称
*/
private List<String> headTitle;
/**
* Excel表头名称映射的Java对象属性名称
*/
private String field;
/**
* 主排序
*/
private int index;
/**
* 次排序
*/
private int order;
/**
* 升序排序
* @param o
* @return
*/
@Override
public int compareTo(HeadVO o) {
if ( == ()) {
return - ();
}
return - ();
}
}
最后是一个基于Spring cglib的Map<==>Java Bean之间的转换工具
package ;
import ;
import ;
import .*;
/**
* bean和map互转 工具类
* <p>
* 使用到spring的cglib
*/
public class BeanMapUtil {
/**
* 将Bean转为Map
*
* @param bean
* @param <T>
* @return
*/
public static <T> Map<String, ?> beanToMap(T bean) {
BeanMap beanMap = (bean);
Map<String, Object> map = new HashMap<>();
((key, value) -> ((key), value));
return map;
}
/**
* 将Map转为Bean
*
* @param map
* @param beanClazz
* @param <T>
* @return
*/
public static <T> T mapToBean(Map<String, ?> map, Class<T> beanClazz) {
T bean;
try {
bean = ();
} catch (InstantiationException | IllegalAccessException e) {
();
throw new PlatformException("Map集合转换到Bean失败");
}
BeanMap beanMap = (bean);
(map);
return bean;
}
/**
* 将一组Beans转为List
*
* @param dataList
* @param <T>
* @return
*/
public static <T> List<Map<String, ?>> beansToMaps(List<T> dataList) {
List<Map<String, ?>> list = new ArrayList<>();
if ((dataList)) {
return ();
}
Map<String, ?> map;
T bean;
for (T t : dataList) {
bean = t;
map = beanToMap(bean);
(map);
}
return list;
}
/**
* 将一组Map转为一组Beans
*
* @param dataMaps
* @param beanClazz
* @param <T>
* @return
*/
public static <T> List<T> mapsToBeans(List<Map<String, ?>> dataMaps, Class<T> beanClazz) {
List<T> list = new ArrayList<>();
if ((dataMaps)) {
return ();
}
Map<String, ?> map;
for (Map<String, ?> dataMap : dataMaps) {
map = dataMap;
T bean = mapToBean(map, beanClazz);
(bean);
}
return list;
}
}