在日常开发中,Excel 文件的导入导出是非常常见的需求。无论是数据批量导入、报表生成还是数据备份,我们都离不开对 Excel 的操作。但传统的 POI 框架在处理大数据量 Excel 时,常常会遇到内存溢出的问题,让开发者头疼不已。

今天给大家介绍一款阿里开源的 Excel 处理工具 ——EasyExcel,它以低内存占用为核心优势,完美解决了大数据量 Excel 处理的痛点。

什么是 EasyExcel?

EasyExcel 是阿里巴巴开源的一个基于 Java 的 Excel 处理工具,它重写了 POI 对 Excel 的解析方式,通过事件驱动模式增量解析的方式,在读取 Excel 时不会将整个文件加载到内存中,而是逐行解析,大大降低了内存占用。

项目地址:https://github.com/alibaba/easyexcel

EasyExcel 的核心优势

  1. 内存占用极低

    • 传统 POI 解析 Excel 时,会将整个文档加载到内存,对于百万级数据的 Excel,很容易导致 OOM
    • EasyExcel 采用逐行解析模式,内存占用可以控制在 KB 级别
  2. API 简洁易用

    • 封装了复杂的 Excel 解析逻辑,提供简单直观的 API
    • 注解驱动,通过注解即可完成 Excel 与实体类的映射
  3. 功能完善

    • 支持 Excel 的读写操作
    • 支持 xls、xlsx 等多种格式
    • 支持复杂表头、合并单元格等复杂场景
    • 支持大数据量的导入导出
  4. 扩展性强

    • 提供丰富的监听器接口,可以自定义处理逻辑
    • 支持自定义转换器,处理特殊格式数据

快速入门:EasyExcel 基本使用

1. 引入依赖

首先在项目中引入 EasyExcel 的 Maven 依赖

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.0</version>
</dependency>

2. 定义实体类

通过注解定义 Excel 与实体类的映射关系

@Data
public class UserData {// index表示列的索引,value表示列名@ExcelProperty(index = 0, value = "姓名")private String name;@ExcelProperty(index = 1, value = "年龄")private Integer age;@ExcelProperty(index = 2, value = "邮箱")private String email;// 日期格式化@ExcelProperty(index = 3, value = "注册时间")@DateTimeFormat("yyyy-MM-dd HH:mm:ss")private Date registerTime;
}

3. 写入 Excel 文件

public class ExcelWriteDemo {public static void main(String[] args) {// 准备数据List<UserData> dataList = new ArrayList<>();for (int i = 0; i < 10; i++) {UserData data = new UserData();data.setName("用户" + i);data.setAge(20 + i);data.setEmail("user" + i + "@example.com");data.setRegisterTime(new Date());dataList.add(data);}// 写入文件String fileName = "D:/user_data.xlsx";EasyExcel.write(fileName, UserData.class).sheet("用户列表")  // 指定工作表名称.doWrite(dataList); // 写入数据}
}

4. 读取 Excel 文件

读取 Excel 需要定义一个监听器:

// 自定义监听器
public class UserDataListener extends AnalysisEventListener<UserData> {// 每解析一行数据就会调用一次@Overridepublic void invoke(UserData data, AnalysisContext context) {System.out.println("解析到数据:" + data);// 可以在这里处理数据,如存入数据库}// 所有数据解析完成后调用@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {System.out.println("所有数据解析完成");}
}// 读取Excel
public class ExcelReadDemo {public static void main(String[] args) {String fileName = "D:/user_data.xlsx";EasyExcel.read(fileName, UserData.class, new UserDataListener()).sheet()  // 读取第一个工作表.doRead(); // 开始读取}
}

Web 场景下的 Excel 导出

在 Web 项目中,我们经常需要实现 Excel 导出功能,让用户可以直接下载文件:

@RequestMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {// 准备数据List<UserData> dataList = getUserDataList();// 设置响应头response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("UTF-8");String fileName = URLEncoder.encode("用户数据", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");// 写入响应流EasyExcel.write(response.getOutputStream(), UserData.class).sheet("用户列表").doWrite(dataList);
}

高级特性

  1. 大数据量处理

EasyExcel 专门为大数据量场景设计,即使处理百万级数据也不会出现内存问题:

// 读取大数据量Excel
EasyExcel.read(fileName, UserData.class, new UserDataListener()).batchRead(1000)  // 批量读取,每1000条处理一次.sheet().doRead();
  1. 复杂表头处理

支持多级表头的导入导出:

// 定义复杂表头
List<List<String>> head = new ArrayList<>();
head.add(Arrays.asList("用户信息", "姓名"));
head.add(Arrays.asList("用户信息", "年龄"));
head.add(Arrays.asList("联系信息", "邮箱"));// 写入复杂表头
EasyExcel.write(fileName).head(head).sheet("复杂表头示例").doWrite(dataList);
  1. 数据转换与格式化

通过自定义转换器处理特殊格式的数据:

// 自定义转换器
public class CustomConverter implements Converter<LocalDateTime> {@Overridepublic Class<LocalDateTime> supportJavaTypeKey() {return LocalDateTime.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}// 读取时转换@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) {return LocalDateTime.parse(context.getReadCellData().getStringValue(), DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"));}// 写入时转换@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) {return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")));}
}

一个完整的ExcelUtils类


public class ExcelUtils {/***   读取前端上传的excel文件*/public static <T> void readAnalysis(MultipartFile file, Class<T> head, ExcelFinishCallBack<T> callBack) {try {EasyExcel.read(file.getInputStream(), head, new ExcelDataListener<>(callBack)).sheet().doRead();} catch (IOException e) {e.printStackTrace();}}/*** 读取本地excel文件** @param <T>      数据类型* @param file     excel文件* @param head     列名* @param callBack 回调 导入时传入定义好的回调接口,excel数据解析完毕之后监听器将数据传入回调函数*                 这样调用工具类时可以通过回调函数获取导入的数据,如果数据量过大可根据实际情况进行分配入库*/public static <T> void readAnalysis(File file, Class<T> head, ExcelFinishCallBack<T> callBack) {try {EasyExcel.read(new FileInputStream(file), head, new ExcelDataListener<>(callBack)).sheet().doRead();} catch (IOException e) {e.printStackTrace();}}/*** 读取excel文件 同步** @param <T>   数据类型* @param file  文件* @param clazz 模板类* @return java.util.List*/public static <T> List<T> readSync(File file, Class<T> clazz) {return readSync(file, clazz, 1, 0, ExcelTypeEnum.XLSX);}/*** 读取excel文件 同步** @param <T>       数据类型* @param file      文件* @param clazz     模板类* @param rowNum    数据开始行 1* @param sheetNo   第几张表* @param excelType 数据表格式类型* @return java.util.List list*/public static <T> List<T> readSync(File file, Class<T> clazz, Integer rowNum, Integer sheetNo, ExcelTypeEnum excelType) {return EasyExcel.read(file).headRowNumber(rowNum).excelType(excelType).head(clazz).sheet(sheetNo).doReadSync();}/*** 导出数据到文件** @param <T>  数据类型* @param head 类名* @param file 导入到本地文件* @param data 数据*/public static <T> void excelExport(Class<T> head, File file, List<T> data) {excelExport(head, file, "sheet1", data);}/*** 导出数据到文件** @param <T>       写入格式* @param head      类名* @param file      写入到文件* @param sheetName sheet名称* @param data      数据列表*/public static <T> void excelExport(Class<T> head, File file, String sheetName, List<T> data) {try {EasyExcel.write(file, head).sheet(sheetName).doWrite(data);} catch (Exception e) {throw new RuntimeException(e);}}/*** 导出数据到web* 文件下载(失败了会返回一个有部分数据的Excel)** @param head      类名* @param excelName excel名字* @param sheetName sheet名称* @param data      数据*                  数据导出到web响应*/public static <T> void excelExport(Class<T> head, String excelName, String sheetName, List<T> data) {try {HttpServletResponse response = getExportResponse(excelName);EasyExcel.write(response.getOutputStream(), head).sheet(StringUtils.isBlank(sheetName) ? "sheet1" : sheetName).doWrite(data);} catch (IOException e) {throw new RuntimeException(e);}}/*** 导出数据到web* 文件下载(失败了会返回一个有部分数据的Excel)** @param head      类名* @param excelName excel名字* @param sheetName sheet名称* @param data      数据*/public static <T> void excelExport(List<List<String>> head, String excelName, String sheetName, List<T> data) {try {HttpServletResponse response = getExportResponse(excelName);EasyExcel.write(response.getOutputStream()).head(head).sheet(StringUtils.isBlank(sheetName) ? "sheet1" : sheetName).doWrite(data);} catch (IOException e) {throw new RuntimeException(e);}}/*设置编码格式,允许前端访问文件名进行跨域审核
*/private static HttpServletResponse getExportResponse(String excelName) {//获得当前HTTP响应对象HttpServletResponse response = HttpContextUtils.getHttpServletResponse();//告诉浏览器返回的是excel文件response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");//允许前端读取文件名 filename:指定下载时显示的文件名response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");//设置编码格式response.setCharacterEncoding("UTF-8");//将文件名进行编码String fileName = URLUtil.encode(excelName, StandardCharsets.UTF_8);//允许前段JavaScript访问Content-Disposition头 获取文件名response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");return response;}/*** 解析字典数据到字段上* 比如 T中有 genderLabel字段 为男 需要给 gender 字段自动设置为0** @param dataList 需要被反向解析的数据*/@SneakyThrowspublic static <T extends TransPojo> void parseDict(List<T> dataList) {//没有数据就不需要初始化if (CollectionUtil.isEmpty(dataList)) {return;}Class<? extends TransPojo> clazz = dataList.get(0).getClass();//拿到所有需要反向翻译的字段  注解带Trans注解的字段List<Field> fields = ReflectUtils.getAnnotationField(clazz, Trans.class);//过滤出类型为字典TransType.DICTIONARY的字段fields = fields.stream().filter(field -> TransType.DICTIONARY.equals(field.getAnnotation(Trans.class).type())).collect(Collectors.toList());//从spring容器中获取字典转换服务DictionaryTransService dictionaryTransService = SpringUtil.getBean(DictionaryTransService.class);//反射设置值for (T data : dataList) {//获取关联字段的值for (Field field : fields) {//从字典中服务中获取映射值Trans trans = field.getAnnotation(Trans.class);// key不能为空并且ref不为空的才自动处理if (StrUtil.isAllNotBlank(trans.key(), trans.ref())) {
//                    根据字段名获取对应的Field对象  类似于user类中的gender字段Field ref = ReflectUtils.getDeclaredField(clazz, trans.ref());//打开访问私有属性的开关ref.setAccessible(true);// 获取字典映射值String value = dictionaryTransService.getDictionaryTransMap().get(trans.key() + "_" + ref.get(data));if (StringUtils.isBlank(value)) {continue;}// 一般目标字段是int或者string字段 后面有添加单独抽离方法if (Integer.class.equals(field.getType())) {field.setAccessible(true);field.set(data, ConverterUtils.toInteger(value));} else {field.setAccessible(true);field.set(data, ConverterUtils.toString(value));}}}}}}

总结

EasyExcel 作为一款优秀的 Excel 处理工具,凭借其低内存占用、简单易用的特点,已经成为 Java 开发中处理 Excel 的首选框架。无论是简单的 Excel 导入导出,还是复杂的大数据量处理场景,EasyExcel 都能轻松应对。

如果你还在为 POI 的内存问题烦恼,不妨试试 EasyExcel,相信它会给你带来惊喜!

欢迎在评论区分享你使用 EasyExcel 的经验和技巧~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98097.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98097.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98097.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件启动时加配置文件 vs 不加配置文件

一、基本概念不加配置文件启动直接执行启动命令&#xff0c;使用软件自带的默认参数。方便、快速&#xff0c;适合测试环境。缺点&#xff1a;灵活性差、配置不可控、不安全。redis-server zookeeper-server-start.sh kafka-server-start.sh指定配置文件启动启动时加载外部配置…

[ubuntu][C++]onnxruntime安装cpu版本后测试代码

下载官方预编译包后&#xff0c;怎么用呢。可以参考这个源码跑测试环境&#xff1a;ubuntu22.04onnxruntime1.18.0测试代码&#xff1a;CMakeLists.txtcmake_minimum_required(VERSION 3.12) project(onnx_test)# 设置C标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD…

栈:有效的括号

题目描述&#xff1a;给定一个只包含‘[’,{,(,),},]的字符串&#xff0c;判断该字符串是否括号有效。 括号有效的要求是&#xff1a; 每个左括号都有对应的右括号。每个右括号都有对应的左括号。左括号必须以正确的顺序闭合。 示例 1&#xff1a; 输入&#xff1a;s "…

微前端架构:解构前端巨石应用的艺术

在数字化转型浪潮中&#xff0c;企业级前端应用正变得日益复杂。微前端架构作为一种创新的解决方案&#xff0c;正在重新定义大型前端应用的构建方式&#xff0c;使多个团队能够独立开发和部署功能模块一、微前端架构的核心价值理念微前端架构的本质是将后端微服务理念扩展到前…

《LangChain从入门到精通》系统学习教材大纲

&#x1f4da; 《LangChain从入门到精通》系统学习教材大纲 目标&#xff1a;帮助你系统掌握LangChain理论与实践&#xff0c;成为具备独立开发能力的AI应用开发者。 学习周期建议&#xff1a;8~12周&#xff08;每天2~3小时&#xff09;&#xff0c;配合项目实战可加速成长。 …

Redis 的相关文件作用

Java 项目中使用 Redis 的相关文件&#xff0c;下面我来逐一解释它们的作用&#xff1a;1. RedisDemoApplicationTests.java 作用&#xff1a;这是 Spring Boot 项目的测试类。用途&#xff1a; 通常用于写单元测试或集成测试。测试 Redis 功能是否正常&#xff0c;比如连接、读…

【React】性能提升方案:Reat.memo, useMemo,useCallback用法详解

前言&#xff1a;Reat.memo, useMemo,useCallback是React中用于性能优化的三个核心API&#xff0c;它们分别针对组件渲染&#xff0c;计算缓存和函数引用进行优化。一、React.memo作用&#xff1a;缓存组件&#xff0c;当父组件重新渲染时&#xff0c;若子组件的props未变化&am…

Alibaba Cloud Linux 3 安装Docker

Alibaba Cloud Linux 3 基于 Red Hat Enterprise Linux (RHEL) 兼容内核&#xff0c;安装 Docker 的步骤与 RHEL/CentOS 系列类似&#xff0c;以下是具体操作&#xff1a; 1. 卸载旧版本&#xff08;如有&#xff09; sudo dnf remove docker docker-client docker-client-la…

每日一练001.pm

题目详情&#xff1a; P5705 【深基2.例7】数字反转 - 洛谷 题目描述 输入一个不小于 100 且小于 1000&#xff0c;同时包括小数点后一位的一个浮点数&#xff0c; 例如 123.4 &#xff0c;要求把这个数字翻转过来&#xff0c;变成 4.321 并输出。 #include<iostream&g…

AI智能优化SEO关键词策略实战

本文聚焦AI如何智能优化SEO关键词策略&#xff0c;通过实战案例分享高效技巧&#xff0c;帮助提升网站搜索排名和流量转化效果。内容涵盖AI革新关键词策略的原理、智能优化技巧的实际应用、高效关键词布局方法、避免常见错误的实战指南&#xff0c;以及综合策略推动排名飞跃的路…

360° 拖动旋转的角度计算原理

360 拖动旋转的角度计算原理 简化的 正方形 div demo 专注讲清楚「点击 / 拖动如何计算角度」这个原理&#xff0c;没有精美 UI哦 中间标注中心点鼠标点击或拖动时&#xff0c;计算当前位置相对于中心的角度在页面上实时显示角度代码示例&#xff08;原生 HTML JS&#xff09;…

五分钟XML速成

原文链接&#xff1a; XML - Dive Into Python 3 深入探讨 本书几乎所有章节都围绕一段示例代码展开&#xff0c;但 XML 并非关于代码&#xff0c;而是关于数据。 XML 的一个常见用途是 “聚合提要”&#xff08;syndication feeds&#xff09;&#xff0c;用于列出博客、论坛…

如何直接访问docker容器中的端口服务而不需要改端口映射

查看docker容器对于宿主服务器的ip地址 docker inspect -f {{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}} $容器名 # 替换$容器名 为自己的启动docker内的服务&#xff0c;监听端口是否用信息 curl http://172.17.0.2:90有信息就可以直接通过该ip访问docker容器端口…

《Istio故障溯源:从流量劫持异常到服务网格的底层博弈》

服务网格常被企业视为微服务通信复杂性的“终极方案”。不少团队在部署Istio时,往往满足于“控制面启动、Sidecar注入成功”的表层验证,却忽视了底层机制与业务场景的深度适配—这种“重部署轻调优”的心态,往往为后续的生产故障埋下隐患。某大型金融机构的核心交易中台在接…

第24节:3D音频与空间音效实现

第24节&#xff1a;3D音频与空间音效实现 概述 3D音频是构建沉浸式体验的关键组件&#xff0c;它通过模拟真实世界中的声音传播特性&#xff0c;为用户提供空间感知和方向感。本节将深入探讨Web Audio API与Three.js的集成&#xff0c;涵盖空间音效原理、音频可视化、多声道处理…

一步搞清楚本地客户端和全局服务器是如何更新模型的

我们可以把它想象成一个 “老师”和“学生” 协作学习的过程。全局服务器 “老师”本地客户端 “学生”整个模型更新的过程遵循一个核心原则&#xff1a;“数据不动&#xff0c;模型动”。原始数据永远留在本地客户端&#xff0c;只有模型的参数&#xff08;即模型的“知识”…

跨平台超低延迟RTSP播放器技术设计探究

摘要 RTSP 播放在实验室里“跑起来”并不难&#xff0c;难的是在真实场景中做到 超低延迟、跨平台、高稳定&#xff0c;并长期可靠运行。大牛直播SDK&#xff08;SmartMediaKit&#xff09;的全自研跨平台 RTSP 播放栈&#xff0c;正是把这些工程难题转化为可用、可控、可交付的…

知识点汇集——web(三)

1.index.php 的备份文件名通常为index.php.bak 2.PHP2是服务器端脚本语言&#xff0c;主要用于处理和生成网页的内容&#xff0c;当用户访问一个网站时&#xff0c;PHP脚本会在服务器上执行&#xff0c;生成动态的HTML页面&#xff0c;然后将页面发送给用户的浏览器进行显示。p…

变频器【简易PLC】功能中的时间问题

一、变频器的简易PLC功能简易PLC功能是将提前设置好的多端速频率&#xff0c;进行自动运行&#xff0c;类似于PLC程序中的CASE指令一样&#xff0c;我们需要提前设置好几段频率&#xff0c;该频率所维持的时间&#xff0c;以及加减速时间&#xff0c;按下启动后&#xff0c;变频…

Swift 解题:LeetCode 372 超级次方(Super Pow)

文章目录摘要描述题解答案题解代码分析代码解析示例测试及结果时间复杂度空间复杂度总结摘要 在算法题里&#xff0c;有一些问题看似“简单”&#xff0c;比如算一个幂次方&#xff0c;但一旦放大规模就完全不同了。LeetCode 372 超级次方就是这样的题目。普通的幂运算没什么难…