基于EasyExcel封装的Excel工具类,支持高效导出和读取操作

news/2025/2/27 6:47:03

以下是一个基于EasyExcel封装的Excel工具类,支持高效导出和读取操作:

java">import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

/**
 * Excel工具类(基于EasyExcel)
 */
public class ExcelUtil {

    /**
     * 导出Excel到输出流(单个Sheet)
     * @param outputStream 输出流
     * @param dataList 数据列表
     * @param templateClass 数据模型类
     * @param sheetName 工作表名称
     * @param <T> 数据类型
     */
    public static <T> void exportToStream(OutputStream outputStream, 
                                        List<T> dataList,
                                        Class<T> templateClass,
                                        String sheetName) {
        ExcelWriter excelWriter = null;
        try {
            // 设置表头样式
            WriteCellStyle headStyle = new WriteCellStyle();
            headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            
            // 设置正文样式
            WriteCellStyle contentStyle = new WriteCellStyle();
            contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);

            // 构建样式策略
            HorizontalCellStyleStrategy styleStrategy = 
                new HorizontalCellStyleStrategy(headStyle, contentStyle);

            excelWriter = EasyExcel.write(outputStream, templateClass)
                    .registerWriteHandler(styleStrategy)
                    .build();

            WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
            excelWriter.write(dataList, writeSheet);
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 导出大数据量Excel(分Sheet写入)
     * @param outputStream 输出流
     * @param dataSupplier 数据提供函数(分页查询)
     * @param templateClass 数据模型类
     * @param sheetSize 每个Sheet最大行数
     * @param <T> 数据类型
     */
    public static <T> void exportBigData(OutputStream outputStream,
                                        PageDataSupplier<T> dataSupplier,
                                        Class<T> templateClass,
                                        int sheetSize) {
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(outputStream, templateClass).build();
            
            int sheetIndex = 1;
            int page = 1;
            List<T> dataChunk;
            
            while (!(dataChunk = dataSupplier.getPageData(page, sheetSize)).isEmpty()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + sheetIndex).build();
                excelWriter.write(dataChunk, writeSheet);
                
                if (page % (sheetSize / 5000) == 0) { // 每5000行一个Sheet
                    sheetIndex++;
                }
                page++;
            }
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 从输入流读取Excel数据
     * @param inputStream 输入流
     * @param templateClass 数据模型类
     * @param dataConsumer 数据消费接口
     * @param <T> 数据类型
     */
    public static <T> void readFromStream(InputStream inputStream,
                                         Class<T> templateClass,
                                         DataConsumer<T> dataConsumer) {
        EasyExcel.read(inputStream, templateClass, new AnalysisEventListener<T>() {
            private static final int BATCH_SIZE = 2000;
            private List<T> cachedList = new ArrayList<>(BATCH_SIZE);

            @Override
            public void invoke(T data, AnalysisContext context) {
                cachedList.add(data);
                if (cachedList.size() >= BATCH_SIZE) {
                    dataConsumer.consume(cachedList);
                    cachedList = new ArrayList<>(BATCH_SIZE);
                }
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                if (!cachedList.isEmpty()) {
                    dataConsumer.consume(cachedList);
                }
            }
        }).sheet().doRead();
    }

    /**
     * 导出Web响应(自动设置Header)
     * @param response HttpServletResponse
     * @param fileName 文件名(不带后缀)
     * @param dataList 数据列表
     * @param templateClass 数据模型类
     * @param <T> 数据类型
     */
    public static <T> void exportWebResponse(HttpServletResponse response,
                                            String fileName,
                                            List<T> dataList,
                                            Class<T> templateClass) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
        exportToStream(response.getOutputStream(), dataList, templateClass, "Sheet1");
    }

    // 自定义转换器注册
    public static void registerConverter(Converter<?> converter) {
        EasyExcel.registerConverter(converter);
    }

    /**
     * 分页数据提供接口
     */
    public interface PageDataSupplier<T> {
        List<T> getPageData(int pageNumber, int pageSize);
    }

    /**
     * 数据消费接口
     */
    public interface DataConsumer<T> {
        void consume(List<T> dataBatch);
    }

    // 示例数据模型
    public static class UserExportModel {
        @ExcelProperty("用户ID")
        private Long userId;
        
        @ExcelProperty(value = "用户状态", converter = StatusConverter.class)
        private Integer status;
        
        // 其他字段...
    }

    // 示例转换器
    public static class StatusConverter implements Converter<Integer> {
        private static final Map<Integer, String> STATUS_MAP = Map.of(
            1, "正常",
            2, "冻结",
            3, "注销"
        );

        @Override
        public String convertToExcelData(Integer value, ExcelContentProperty property, 
                                       GlobalConfiguration globalConfig) {
            return STATUS_MAP.getOrDefault(value, "未知状态");
        }
    }
}

使用示例

1. 简单导出到文件:

java">try (FileOutputStream fos = new FileOutputStream("export.xlsx")) {
    List<User> userList = userService.getAllUsers();
    ExcelUtil.exportToStream(fos, userList, User.class, "用户数据");
}

2. Web响应导出:

java">@GetMapping("/export")
public void exportUsers(HttpServletResponse response) throws IOException {
    List<User> userList = userService.getAllUsers();
    ExcelUtil.exportWebResponse(response, "用户列表", userList, User.class);
}

3. 大数据量分页导出:

java">ExcelUtil.exportBigData(response.getOutputStream(), 
    (page, size) -> userService.getUsersByPage(page, size),
    User.class,
    100_0000); // 每个Sheet存储100万数据

4. 读取Excel数据:

java">InputStream inputStream = new FileInputStream("import.xlsx");
ExcelUtil.readFromStream(inputStream, UserImportModel.class, batch -> {
    userService.batchProcess(batch);
});

主要特性说明

  1. 高性能导出:

    • 使用分Sheet写入策略,每个Sheet最多存储指定行数
    • 自动注册样式策略(表头居中,内容左对齐)
    • 支持流式导出,避免内存溢出
  2. 灵活读取:

    • 批量处理机制(默认每2000条处理一次)
    • 自动内存管理,适合大文件读取
  3. 扩展功能:

    • 支持自定义转换器(通过registerConverter方法)
    • 支持Web响应自动配置(自动设置Content-Type和文件名)
    • 提供分页数据获取接口,适合数据库分页查询
  4. 内存安全:

    • 导出时自动分批次写入
    • 读取时使用事件驱动模型,不保留完整数据在内存
    • 默认开启SXSSF模式(流式写入)
  5. 样式配置:

    • 预定义表头和内容样式
    • 可通过覆盖registerWriteHandler方法自定义样式

最佳实践建议

  1. 大文件导出:

    • 使用exportBigData方法配合分页查询
    • 设置合适的Sheet大小(推荐50-100万行/Sheet)
    • 添加内存监控逻辑
  2. 数据转换:

    • 为枚举字段创建专用转换器
    • 对日期字段使用@ExcelProperty#format
    • 复杂转换建议使用数据库联查
  3. 异常处理:

    • 在读取时添加数据校验逻辑
    • 使用全局异常处理器捕获ExcelAnalysisException
  4. 性能优化:

    • 导出时关闭自动列宽计算(.autoTrim(false)
    • 读取时设置合适的缓存大小(ReadCache
    • 避免在循环中创建大量临时对象

http://www.niftyadmin.cn/n/5869675.html

相关文章

如何在 Mac 上使用 Xcode 将 Chrome 插件转换为 Safari 插件 (一些插件)

如何在 Mac 上使用 Xcode 将 Chrome 插件转换为 Safari 插件 1. 安装 Xcode 和 Safari • 从 Mac App Store 下载并安装 Xcode 和 Safari&#xff08;通常 macOS 自带&#xff09;。 • 运行以下命令初始化 Xcode&#xff08;如果是第一次使用&#xff09;&#xff1a; sudo…

使用Jenkins实现Windows服务器下C#应用程序发布

背景 在现代化的软件开发流程中&#xff0c;持续集成和持续部署&#xff08;CI/CD&#xff09;已经成为不可或缺的一部分。 Jenkins作为一款开源的自动化运维工具&#xff0c;能够帮助我们实现这一目标。 本文将详细介绍如何在Windows服务器下使用Jenkins来自动化发布C#应用…

minio多主机分布式部署

Minio多主机分布式 docker-compose 集群部署_minio docker-compose-CSDN博客

WebStorm 安装配置(详细教程)

文章目录 一、简介二、优势三、下载四、安装4.1 开始安装4.2 选择安装路径4.3 安装选项4.4 选择开始菜单文件夹4.5 安装完成 五、常用插件5.1 括号插件&#xff08;Rainbow Brackets&#xff09;5.2 翻译插件&#xff08;Translation&#xff09;5.3 代码缩略图&#xff08;Cod…

仿12306购票系统(3)

前面完成了乘车人登录功能的实现&#xff0c;本篇主要是控制台方面的管理 对于整体的控制台的设计&#xff0c;为了能够快速的检验&#xff0c;不进行登录拦截&#xff0c;在控制台的这个模块的controller层增加admin&#xff0c;以及在登录界面的拦截器排除掉admin. 车站 即…

【机器学习】 [代码篇] 30. KNN - sklearn 以及 自定义KNN 的实现

KNN - sklearn 以及 自定义KNN 的实现 前言Github 链接使用SKlearn 库完成KNN的训练以及预测1. 导入需要的库2. 加载数据2.1. 输出数据信息 3. 分割训练集和测试集4. 可视化5. 创建模型并预测 2. 自定义KNN模型并预测 前言 前面写完了理论篇&#xff0c;接下来补充代码。 机器…

应对现代生活的健康养生指南

在科技飞速发展的现代社会&#xff0c;人们的生活方式发生了巨大改变&#xff0c;随之而来的是一系列健康问题。快节奏的生活、高强度的工作以及电子产品的过度使用&#xff0c;让我们的身体承受着前所未有的压力。因此&#xff0c;掌握正确的健康养生方法迫在眉睫。 针对久坐不…

PCL源码分析:曲面法向量采样

文章目录 一、简介二、源码分析三、实现效果参考资料一、简介 曲面法向量点云采样,整个过程如下所述: 1、空间划分:使用递归方法将点云划分为更小的区域, 每次划分选择一个维度(X、Y 或 Z),将点云分为两部分,直到划分区域内的点少于我们指定的数量,开始进行区域随机采…