解决EasyExcel工具读取Excel空数据行的问题

时间:2025-03-04 11:04:15

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;
    }
}