EasyExcel工具讀取Excel空數(shù)據(jù)行問題的解決辦法
EasyExcel是Alibaba開源的一個Java處理Excel的工具。
官網(wǎng)解讀:快速、簡潔、解決大文件內(nèi)存溢出的java處理Excel工具
快速
快速的讀取excel中的數(shù)據(jù)。
簡潔
映射excel和實體類,讓代碼變的更加簡潔。
大文件
在讀寫大文件的時候使用磁盤做緩存,更加的節(jié)約內(nèi)存。
官網(wǎng)地址:https://easyexcel.opensource.alibaba.com/
感興趣可自己琢磨,該工具簡單易上手,且性能相對比較高。
本文主要處理的問題是該工具讀取Excel空數(shù)據(jù)行的問題。
首先解釋為什么會產(chǎn)生空數(shù)據(jù)行:簡單解釋就是你在Excel中設(shè)置了單元的樣式,卻沒有給單元格設(shè)值。因此,該工具在讀取數(shù)據(jù)時便沒有判斷這一步,直接讀取到整行數(shù)據(jù)均為null。
理解了核心問題后,要解決這個問題,實現(xiàn)思路也不難。
莫非就是把這種空數(shù)據(jù)行過濾即可。
本文是基于批處理監(jiān)聽器實現(xiàn)數(shù)據(jù)讀取的,自定義集成該監(jiān)聽器(com.alibaba.excel.read.listener.PageReadListener),實現(xiàn)自己的邏輯即可解決問題。
下面是自定義監(jiān)聽器
package xin.cosmos.basic.easyexcel.framework;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.util.ListUtils;
import lombok.extern.slf4j.Slf4j;
import xin.cosmos.basic.util.ObjectsUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* 自定義分批Excel數(shù)據(jù)讀取監(jiān)聽器,解決官方無法移除空的Excel行問題
*
* @param <T>
* @author geng
*/
@Slf4j
public class BatchPageReadListener<T> extends PageReadListener<T> {
/**
* Temporary storage of data
*/
private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* consumer
*/
private final Consumer<List<T>> consumer;
public BatchPageReadListener(Consumer<List<T>> consumer) {
super(consumer);
this.consumer = consumer;
}
@Override
public void invoke(T data, AnalysisContext context) {
// 如果一行Excel數(shù)據(jù)均為空值,則不裝載該行數(shù)據(jù)
if (isLineNullValue(data)) {
return;
}
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
consumer.accept(cachedDataList);
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (ObjectsUtil.isNull(cachedDataList)) {
return;
}
consumer.accept(cachedDataList);
}
/**
* 判斷整行單元格數(shù)據(jù)是否均為空
*/
private boolean isLineNullValue(T data) {
if (data instanceof String) {
return ObjectsUtil.isNull(data);
}
try {
List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields())
.filter(f -> f.isAnnotationPresent(ExcelProperty.class))
.collect(Collectors.toList());
List<Boolean> lineNullList = new ArrayList<>(fields.size());
for (Field field : fields) {
field.setAccessible(true);
Object value = field.get(data);
if (ObjectsUtil.isNull(value)) {
lineNullList.add(Boolean.TRUE);
} else {
lineNullList.add(Boolean.FALSE);
}
}
return lineNullList.stream().allMatch(Boolean.TRUE::equals);
} catch (Exception e) {
log.error("讀取數(shù)據(jù)行[{}]解析失敗: {}", data, e.getMessage());
}
return true;
}
}下面是我對EasyExcel封裝的工具類
package xin.cosmos.basic.easyexcel.helper;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import xin.cosmos.basic.define.ResultVO;
import xin.cosmos.basic.easyexcel.framework.BatchPageReadListener;
import xin.cosmos.basic.easyexcel.template.HeadVO;
import xin.cosmos.basic.exception.PlatformException;
import xin.cosmos.basic.util.BeanMapUtil;
import xin.cosmos.basic.util.ObjectsUtil;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
/**
* EasyExcel 幫助類
*/
@Slf4j
public class EasyExcelHelper {
/**
* 讀取Excel文件
*
* @param stream 文件流
* @param entityClass 讀取轉(zhuǎn)換的Java對象類型
* @param <T>
* @return
*/
public static <T> List<T> doReadExcelData(InputStream stream, Class<T> entityClass) {
List<T> data = new LinkedList<>();
EasyExcelFactory.read(stream, entityClass, new PageReadListener<T>(data::addAll)).sheet().doRead();
return data;
}
/**
* 讀取Excel文件
*
* @param stream 文件流
* @param entityClass 讀取轉(zhuǎn)換的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<>();
EasyExcelFactory.read(stream, entityClass, new BatchPageReadListener<T>(list -> {
if (comparator != null) {
list.sort(comparator);
}
data.addAll(list);
})).sheet().doRead();
return data;
}
/**
* 讀取Excel文件
*
* @param file MultipartFile文件
* @param entityClass 讀取轉(zhuǎn)換的Java對象類型
*/
@SneakyThrows
public static <T> List<T> doReadExcelData(MultipartFile file, Class<T> entityClass) {
return doReadExcelData(file.getInputStream(), entityClass);
}
/**
* 讀取Excel文件
*
* @param file File文件
* @param entityClass 讀取轉(zhuǎn)換的Java對象類型
*/
@SneakyThrows
public static <T> List<T> doReadExcelData(File file, Class<T> entityClass) {
List<T> data = new LinkedList<>();
EasyExcelFactory.read(file, entityClass, new BatchPageReadListener<T>(data::addAll)).sheet().doRead();
return data;
}
/**
* Excel數(shù)據(jù)瀏覽器下載
*
* @param pathName 下載文件的完整路徑名稱
* @param data 需下載數(shù)據(jù)
* @param entityClazz 下載數(shù)據(jù)類型模板
*/
@SneakyThrows
public static <T> void downloadExcel(String pathName, List<T> data, Class<T> entityClazz) {
try {
// 構(gòu)建Excel表頭及數(shù)據(jù)體
ExcelWriterSheetBuilder builder = EasyExcel.write(pathName)
.autoCloseStream(true)
.sheet("sheet1");
doWriteWithDynamicColumns(builder, entityClazz, data);
} catch (Exception e) {
log.error("寫文件錯誤:{}", e.getMessage());
throw new PlatformException("Excel下載數(shù)據(jù)錯誤");
}
}
/**
* Excel數(shù)據(jù)瀏覽器下載
* <p>
* 前端下載js代碼示例:
* <pre>
* function downloadFile(bytes, fileName) {
* const blob = new Blob([bytes], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
* if (window.navigator.msSaveOrOpenBlob) { // 兼容IE10
* navigator.msSaveBlob(blob, fileName)
* } else {
* const url = window.URL.createObjectURL(blob);
* const a = document.createElement('a');
* a.href = url;
* a.download = fileName;
* a.click();
* window.URL.revokeObjectURL(url);
* }
* }
* </pre>
*
* @param excelFileName 下載文件名稱
* @param response 響應(yīng)容器
* @param data 需下載數(shù)據(jù)
* @param entityClazz 下載數(shù)據(jù)Bean實體類型,蘇醒必須使用注解<code>@ExcelProperty</code>中value指定寫出列的表頭嗎,名稱
*/
public static <T> void downloadExcelToResponse(HttpServletResponse response, String excelFileName, List<T> data, Class<T> entityClazz) {
if (ObjectsUtil.isNull(data)) {
log.error("寫文件錯誤:{}", "暫無可下載的數(shù)據(jù)");
writeErrMsg(response, "暫無可下載的數(shù)據(jù)");
return;
}
try {
// 這里注意 有同學(xué)反應(yīng)使用swagger 會導(dǎo)致各種問題,請直接用瀏覽器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
if (excelFileName.endsWith(".xlsx") || excelFileName.endsWith(".xls") ||
excelFileName.endsWith(".XLSX") || excelFileName.endsWith(".XLS")) {
excelFileName = excelFileName.substring(0, excelFileName.lastIndexOf("."));
}
// 這里URLEncoder.encode可以防止中文亂碼 當(dāng)然和easy excel沒有關(guān)系
String urlFileName = URLEncoder.encode(excelFileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + urlFileName + ".xlsx");
response.setHeader("excel-file-name", urlFileName + ".xlsx");
// 構(gòu)建Excel表頭及數(shù)據(jù)體
ExcelWriterSheetBuilder builder = EasyExcel.write(response.getOutputStream())
.excelType(ExcelTypeEnum.XLSX)
.autoCloseStream(true)
.sheet("sheet1");
doWriteWithDynamicColumns(builder, entityClazz, data);
} catch (Exception e) {
log.error("寫文件錯誤:{}", e.getMessage());
writeErrMsg(response, e.getMessage());
}
}
/**
* EasyExcel支持動態(tài)列寫數(shù)據(jù)
*
* @param builder 指定輸出方式和樣式
* @param entityClazz 實體的Class對象
* @param data Excel行數(shù)據(jù)
*/
public static <T> void doWriteWithDynamicColumns(ExcelWriterSheetBuilder builder, Class<T> entityClazz, List<T> data) {
List<HeadVO> customizeHeads = new ArrayList<>();
Field[] fieldArray = entityClazz.getDeclaredFields();
// 獲取類的注解
for (Field field : fieldArray) {
// 忽略導(dǎo)出屬性
if (field.isAnnotationPresent(ExcelIgnore.class)) {
continue;
}
if (field.isAnnotationPresent(ExcelProperty.class)) {
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
List<String> head = Arrays.asList(excelProperty.value());
int index = excelProperty.index();
int order = excelProperty.order();
HeadVO headVO = HeadVO.builder().headTitle(head).index(index).order(order).field(field.getName()).build();
customizeHeads.add(headVO);
}
}
// 表頭排序
Collections.sort(customizeHeads);
// 處理表頭
List<List<String>> heads = new ArrayList<>();
List<String> fields = new ArrayList<>();
for (int i = 0; i <= customizeHeads.size() - 1; i++) {
heads.add(customizeHeads.get(i).getHeadTitle());
fields.add(customizeHeads.get(i).getField());
}
// 處理數(shù)據(jù)
List<List<Object>> objs = new ArrayList<>();
List<Map<String, ?>> maps = BeanMapUtil.beansToMaps(data);
maps.forEach(map -> {
List<Object> obj = new ArrayList<>();
for (String field : fields) {
obj.add(map.get(field));
}
objs.add(obj);
});
builder.head(heads).doWrite(objs);
}
@SneakyThrows
private static void writeErrMsg(HttpServletResponse response, String errMsg) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().println(JSON.toJSONString(ResultVO.failed(errMsg)));
}
}package xin.cosmos.basic.easyexcel.template;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 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 (this.index == o.getIndex()) {
return this.order - o.getOrder();
}
return this.index - o.getIndex();
}
}最后是一個基于Spring cglib的Map<==>Java Bean之間的轉(zhuǎn)換工具
package xin.cosmos.basic.util;
import org.springframework.cglib.beans.BeanMap;
import xin.cosmos.basic.exception.PlatformException;
import java.util.*;
/**
* bean和map互轉(zhuǎn) 工具類
* <p>
* 使用到spring的cglib
*/
public class BeanMapUtil {
/**
* 將Bean轉(zhuǎn)為Map
*
* @param bean
* @param <T>
* @return
*/
public static <T> Map<String, ?> beanToMap(T bean) {
BeanMap beanMap = BeanMap.create(bean);
Map<String, Object> map = new HashMap<>();
beanMap.forEach((key, value) -> map.put(String.valueOf(key), value));
return map;
}
/**
* 將Map轉(zhuǎn)為Bean
*
* @param map
* @param beanClazz
* @param <T>
* @return
*/
public static <T> T mapToBean(Map<String, ?> map, Class<T> beanClazz) {
T bean;
try {
bean = beanClazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new PlatformException("Map集合轉(zhuǎn)換到Bean失敗");
}
BeanMap beanMap = BeanMap.create(bean);
beanMap.putAll(map);
return bean;
}
/**
* 將一組Beans轉(zhuǎn)為List
*
* @param dataList
* @param <T>
* @return
*/
public static <T> List<Map<String, ?>> beansToMaps(List<T> dataList) {
List<Map<String, ?>> list = new ArrayList<>();
if (ObjectsUtil.isNull(dataList)) {
return Collections.emptyList();
}
Map<String, ?> map;
T bean;
for (T t : dataList) {
bean = t;
map = beanToMap(bean);
list.add(map);
}
return list;
}
/**
* 將一組Map轉(zhuǎn)為一組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 (ObjectsUtil.isNull(dataMaps)) {
return Collections.emptyList();
}
Map<String, ?> map;
for (Map<String, ?> dataMap : dataMaps) {
map = dataMap;
T bean = mapToBean(map, beanClazz);
list.add(bean);
}
return list;
}
}
總結(jié)
到此這篇關(guān)于EasyExcel工具讀取Excel空數(shù)據(jù)行問題解決的文章就介紹到這了,更多相關(guān)EasyExcel讀取Excel空數(shù)據(jù)行內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于java中byte數(shù)組與int類型的轉(zhuǎn)換(兩種方法)
下面小編就為大家?guī)硪黄趈ava中byte數(shù)組與int類型的轉(zhuǎn)換(兩種方法)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08
Java使用jdbc連接MySQL數(shù)據(jù)庫實例分析
這篇文章主要介紹了Java使用jdbc連接MySQL數(shù)據(jù)庫,結(jié)合實例形式分析了Java基于jdbc鏈接mysql的相關(guān)配置及工具類的定義相關(guān)操作技巧,需要的朋友可以參考下2018-07-07
springCloud gateWay 統(tǒng)一鑒權(quán)的實現(xiàn)代碼
這篇文章主要介紹了springCloud gateWay 統(tǒng)一鑒權(quán)的實現(xiàn)代碼,統(tǒng)一鑒權(quán)包括鑒權(quán)邏輯和代碼實現(xiàn),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02
mybatis如何使用注解實現(xiàn)一對多關(guān)聯(lián)查詢
這篇文章主要介紹了mybatis如何使用注解實現(xiàn)一對多關(guān)聯(lián)查詢的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07

