EasyExcel工具讀取Excel空數(shù)據(jù)行問(wèn)題的解決辦法
EasyExcel是Alibaba開(kāi)源的一個(gè)Java處理Excel的工具。
官網(wǎng)解讀:快速、簡(jiǎn)潔、解決大文件內(nèi)存溢出的java處理Excel工具
快速
快速的讀取excel中的數(shù)據(jù)。
簡(jiǎn)潔
映射excel和實(shí)體類(lèi),讓代碼變的更加簡(jiǎn)潔。
大文件
在讀寫(xiě)大文件的時(shí)候使用磁盤(pán)做緩存,更加的節(jié)約內(nèi)存。
官網(wǎng)地址:https://easyexcel.opensource.alibaba.com/
感興趣可自己琢磨,該工具簡(jiǎn)單易上手,且性能相對(duì)比較高。
本文主要處理的問(wèn)題是該工具讀取Excel空數(shù)據(jù)行的問(wèn)題。
首先解釋為什么會(huì)產(chǎn)生空數(shù)據(jù)行:簡(jiǎn)單解釋就是你在Excel中設(shè)置了單元的樣式,卻沒(méi)有給單元格設(shè)值。因此,該工具在讀取數(shù)據(jù)時(shí)便沒(méi)有判斷這一步,直接讀取到整行數(shù)據(jù)均為null。
理解了核心問(wèn)題后,要解決這個(gè)問(wèn)題,實(shí)現(xiàn)思路也不難。
莫非就是把這種空數(shù)據(jù)行過(guò)濾即可。
本文是基于批處理監(jiān)聽(tīng)器實(shí)現(xiàn)數(shù)據(jù)讀取的,自定義集成該監(jiān)聽(tīng)器(com.alibaba.excel.read.listener.PageReadListener),實(shí)現(xiàn)自己的邏輯即可解決問(wèn)題。
下面是自定義監(jiān)聽(tīng)器
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)聽(tīng)器,解決官方無(wú)法移除空的Excel行問(wèn)題 * * @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; } }
下面是我對(duì)EasyExcel封裝的工具類(lèi)
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 幫助類(lèi) */ @Slf4j public class EasyExcelHelper { /** * 讀取Excel文件 * * @param stream 文件流 * @param entityClass 讀取轉(zhuǎn)換的Java對(duì)象類(lèi)型 * @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對(duì)象類(lèi)型 * @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對(duì)象類(lèi)型 */ @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對(duì)象類(lèi)型 */ @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 下載文件的完整路徑名稱(chēng) * @param data 需下載數(shù)據(jù) * @param entityClazz 下載數(shù)據(jù)類(lèi)型模板 */ @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("寫(xiě)文件錯(cuò)誤:{}", e.getMessage()); throw new PlatformException("Excel下載數(shù)據(jù)錯(cuò)誤"); } } /** * 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 下載文件名稱(chēng) * @param response 響應(yīng)容器 * @param data 需下載數(shù)據(jù) * @param entityClazz 下載數(shù)據(jù)Bean實(shí)體類(lèi)型,蘇醒必須使用注解<code>@ExcelProperty</code>中value指定寫(xiě)出列的表頭嗎,名稱(chēng) */ public static <T> void downloadExcelToResponse(HttpServletResponse response, String excelFileName, List<T> data, Class<T> entityClazz) { if (ObjectsUtil.isNull(data)) { log.error("寫(xiě)文件錯(cuò)誤:{}", "暫無(wú)可下載的數(shù)據(jù)"); writeErrMsg(response, "暫無(wú)可下載的數(shù)據(jù)"); return; } try { // 這里注意 有同學(xué)反應(yīng)使用swagger 會(huì)導(dǎo)致各種問(wèn)題,請(qǐng)直接用瀏覽器或者用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沒(méi)有關(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("寫(xiě)文件錯(cuò)誤:{}", e.getMessage()); writeErrMsg(response, e.getMessage()); } } /** * EasyExcel支持動(dòng)態(tài)列寫(xiě)數(shù)據(jù) * * @param builder 指定輸出方式和樣式 * @param entityClazz 實(shí)體的Class對(duì)象 * @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(); // 獲取類(lèi)的注解 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類(lèi) */ @Builder @Data public class HeadVO implements Comparable<HeadVO> { /** * Excel表頭名稱(chēng) */ private List<String> headTitle; /** * Excel表頭名稱(chēng)映射的Java對(duì)象屬性名稱(chēng) */ 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(); } }
最后是一個(gè)基于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) 工具類(lèi) * <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)為L(zhǎng)ist * * @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ù)行問(wèn)題解決的文章就介紹到這了,更多相關(guān)EasyExcel讀取Excel空數(shù)據(jù)行內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編寫(xiě)計(jì)算器的常見(jiàn)方法實(shí)例總結(jié)
這篇文章主要介紹了Java編寫(xiě)計(jì)算器的常見(jiàn)方法,結(jié)合實(shí)例形式總結(jié)分析了Java實(shí)現(xiàn)計(jì)算器功能的常用方法,需要的朋友可以參考下2016-04-04基于java中byte數(shù)組與int類(lèi)型的轉(zhuǎn)換(兩種方法)
下面小編就為大家?guī)?lái)一篇基于java中byte數(shù)組與int類(lèi)型的轉(zhuǎn)換(兩種方法)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08基于Java實(shí)現(xiàn)遍歷文件目錄并去除中文文件名
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)遍歷文件目錄并去除中文文件名,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2024-03-03Java使用jdbc連接MySQL數(shù)據(jù)庫(kù)實(shí)例分析
這篇文章主要介紹了Java使用jdbc連接MySQL數(shù)據(jù)庫(kù),結(jié)合實(shí)例形式分析了Java基于jdbc鏈接mysql的相關(guān)配置及工具類(lèi)的定義相關(guān)操作技巧,需要的朋友可以參考下2018-07-07springCloud gateWay 統(tǒng)一鑒權(quán)的實(shí)現(xiàn)代碼
這篇文章主要介紹了springCloud gateWay 統(tǒng)一鑒權(quán)的實(shí)現(xiàn)代碼,統(tǒng)一鑒權(quán)包括鑒權(quán)邏輯和代碼實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02關(guān)于feign接口動(dòng)態(tài)代理源碼解析
這篇文章主要介紹了關(guān)于feign接口動(dòng)態(tài)代理源碼解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03mybatis如何使用注解實(shí)現(xiàn)一對(duì)多關(guān)聯(lián)查詢(xún)
這篇文章主要介紹了mybatis如何使用注解實(shí)現(xiàn)一對(duì)多關(guān)聯(lián)查詢(xún)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07