我们在开发应用系统的时候经常遇到操作或解析 Excel 的需求,我们在实现此功能的时候也都是借助 Apach POI 去操作 Excel,但是使用过这个框架的小伙伴都知道,这个框架并不是很好用,比如使用步骤繁琐、动态写出Excel操作非常麻烦、读写时需要占用较大的内容,而且对新手很不友好,上手比较难,这就很让人头疼了…
那么今天就给大家推荐另一种操作 Excel 的框架 —— EasyExcel - 用更简单的方式操作 Excel。
在讲解如何使用 EasyExcel 框架之前,我们先简单的介绍一下 EasyExcel 框架:
EasyExcel 是一个基于Java 的简单、省内存的读写 Excel 的开源框架。它可以在尽可能节约内存的情况下支持读写百兆的 Excel 文件。
EasyExcel 是阿里巴巴开源的一个 Excel 处理框架,以使用简单、节省内存著称,那么它到底有多省内存呢~ 我们接着往下看
它可以在1分钟内读取75M(46W行25列)的 Excel 文件,而且仅占用64M内存(这要是用 Apach POI 去读取同样大小文件,那花费的时间可就不仅仅是一分钟的事了)。那它为什么可以在占用少量内存的前提下做到这么迅速呢?
我们通过百度也找到了答案:EasyExcel 在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个进行解析,并将每一行的解析结果以观察者模式通知处理(AnalysisEventListener),这也就是为什么 EasyExcel 在操作 Excel 文件时能大大减少内存的占用。
咱们已经对 EasyExcel 框架有了一个简单的了解,那么下面就该上重点了,看看我们如何使用 EasyExcel 框架。
在使用之前还是需要先引入 Maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
接下来我们新建一个学生实体类,实体类也很简单,只有三个字段:id、姓名、生日
import java.util.Date;
/**
* @description: Student
* @author: 庄霸.liziye
* @create: 2022-02-21 16:21
**/
public class Student {
private Integer id;
private String name;
private Date birthday;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
接下来我们就通过 EasyExcel 框架来实现向 Excel 文件里写入100条学生信息
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @description: EasyExcelDemo
* @author: 庄霸.liziye
* @create: 2022-02-21 16:26
**/
public class EasyExcelDemo {
public static void main(String[] args) {
List<Student> lists = new ArrayList<>();
for(int i = 0; i <= 100; i++) {
Student student = new Student();
student.setId(i + 1);
student.setName("我是" + i + "号大聪明");
student.setBirthday(new Date());
lists.add(student);
}
EasyExcel.write("学生信息表.xlsx", Student.class).sheet().doWrite(lists);
}
}
我们执行一下代码…代码执行完毕后,在项目的根目录下就生成了一个名为“学生信息表”的 Excel 文件,如下所示
接下来我们对 EasyExcel.write 方法做一个简单的解释:EasyExcel.write 方法表示构建一个Excel写对象,该方法包含了两个参数,第一个参数代表生成的文件名,第二个参数代表写到表格中的数据类型的 Class 对象。我们一起看一下 EasyExcel.write 方法的源码:
通过源码我们可以看到,在构建 Excel 写对象的时候可以通过多种方式构建,具体使用哪种方式还要看具体的需求。
P.S. 眼尖的小伙伴应该看到了示例代码中的.sheet(),sheet() 代表的是在Excel 文件中的哪个 sheet 页写入数据,如果不指定,默认在第一个。
我们通过上面的截图可以看到,写出的数据表头均为实体类的属性名,且列的顺序也是按照类中属性的顺序进行排列的,但是在实际开发过程中,表头均为自定义信息,且顺序也不一定按照属性的顺序来。因此我们需要对学生类做一点小小的修改
import com.alibaba.excel.annotation.ExcelProperty;
import java.util.Date;
/**
* @program: netty
* @description: Student
* @author: 庄霸.liziye
* @create: 2022-02-21 16:21
**/
public class Student {
@ExcelProperty("学生学号")
private Integer id;
@ExcelProperty("学生姓名")
private String name;
@ExcelProperty("学生生日")
private Date birthday;
/** 省略get/set方法 **/
}
当我们需要自定义列的顺序时,可以这样来修改(order 的值越大,列越靠右)
import com.alibaba.excel.annotation.ExcelProperty;
import java.util.Date;
/**
* @program: netty
* @description: Student
* @author: 庄霸.liziye
* @create: 2022-02-21 16:21
**/
public class Student {
@ExcelProperty(value = "学生学号", order = 10)
private Integer id;
@ExcelProperty(value = "学生姓名", order = 2)
private String name;
@ExcelProperty(value = "学生生日", order = 3)
private Date birthday;
/** 省略get/set方法 **/
}
有时候我们还需要在表头上在加上一个表头,我们再修改一下实体类
/**
* @program: netty
* @description: Student
* @author: 庄霸.liziye
* @create: 2022-02-21 16:21
**/
public class Student {
@ExcelProperty(value = {"学生信息", "学生学号"}, order = 10)
private Integer id;
@ExcelProperty(value = {"学生信息", "学生姓名"}, order = 2)
private String name;
@ExcelProperty(value = {"学生信息", "学生生日"}, order = 3)
private Date birthday;
/** 省略get/set方法 **/
}
接下来我们执行一下代码,一起来看看这次生成的 Excel 表格 ✌
咱们说完了写操作,接下来再来看看 EasyExcel 框架的读操作,读取 Excel 文件在实际开发中也占据了较大地位,那么废话不多说,咱们直接上代码(读取我们刚刚生成的 Excel 文件的内容)
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import java.util.ArrayList;
import java.util.List;
/**
* @description: EasyExcelDemo
* @author: 庄霸.liziye
* @create: 2022-02-21 16:26
**/
public class EasyExcelDemo {
public static void main(String[] args) throws Exception {
readExcel();
}
public static void readExcel() throws Exception {
List<Student> list = new ArrayList<>();
/*
* EasyExcel 的读取是基于SAX方式实现的,因此在解析时需要传入监听器
*
* 第一个参数:excel文件路径
* 第二个参数:读取时的数据类型
* 第三个参数:监听器
*/
EasyExcel.read("学生信息表" + ExcelTypeEnum.XLSX.getValue(), Student.class, new AnalysisEventListener<Student>() {
// 每读取一行就调用该方法
@Override
public void invoke(Student data, AnalysisContext context) {
list.add(data);
}
// 全部读取完成就调用该方法
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("读取完成");
}
}).sheet().doRead();
list.forEach(System.out::println);
}
}
为了方便查看结果,我们在实体类中增加一个 toString() 方法
import com.alibaba.excel.annotation.ExcelProperty;
import java.util.Date;
/**
* @program: netty
* @description: Student
* @author: 庄霸.liziye
* @create: 2022-02-21 16:21
**/
public class Student {
@ExcelProperty(value = {"学生信息", "学生学号"}, order = 10)
private Integer id;
@ExcelProperty(value = {"学生信息", "学生姓名"}, order = 2)
private String name;
@ExcelProperty(value = {"学生信息", "学生生日"}, order = 3)
private Date birthday;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", birthday=" + birthday +
'}';
}
}
接下来我们执行一下代码,一起看看 Excel 中的数据有没有被读取出来~
还是老规矩,我们对 EasyExcel.write 方法做一个简单的解释:EasyExcel.read 方法用来创建 ExcelReaderBuilder 对象,我们通过 ExcelReaderBuilder 对 Excel 文档进行解析。EasyExcel.read 方法需要传入三个参数:
P.S. 读中的 sheet() 方法和写中的 sheet() 方法含义是相同的,都代表了要操作 Excel 文件中的第几个 sheet。
EasyExcel 框架的功能是很强大的,其中包含了很多复杂的读写操作,由于篇幅有限,这里就不一一讲解了,直接附上官方文档的链接,有需要的小伙伴可以移步至 EasyExcel 官网 深入学习。
在上面讲到的例子中,读写操作都是在本地操作 Excel 文档,但是在实际开发都是在 web 中操作的,其实本质也都是一样的,只不过数据的来源变了一下,最后给各位小伙伴提供一个 Excel 工具类,希望能帮助到各位小伙伴
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.briup.server.exception.SMSException;
import com.briup.server.logging.LogHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
public class ExcelUtil {
/**
* 写出一个 excel 文件到本地
*
* 将类型所有加了 @ExcelProperty 注解的属性全部写出
*
* @param fileName 文件名 不要后缀
* @param sheetName sheet名
* @param data 写出的数据
* @param clazz 要写出数据类的Class类型对象
* @param 写出的数据类型
*/
public static <T> void writeExcel(String fileName, String sheetName, List<T> data, Class<T> clazz) {
writeExcel(null, fileName, sheetName, data, clazz);
}
/**
* 按照指定的属性名进行写出 一个 excel
*
* @param attrName 指定的属性名 必须与数据类型的属性名一致
* @param fileName 文件名 不要后缀
* @param sheetName sheet名
* @param data 要写出的数据
* @param clazz 要写出数据类的Class类型对象
* @param 要写出的数据类型
*/
public static <T> void writeExcel(Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
fileName = StringUtils.isBlank(fileName) ? "学生管理系统" : fileName;
sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;
try(FileOutputStream fos = new FileOutputStream(fileName)) {
write(fos,attrName,sheetName,data,clazz);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* 读取 指定格式的 excel文档
*
* @param fileName 文件名
* @param clazz 数据类型的class对象
* @param 数据类型
* @return
*/
public static <T> List<T> readExcel(String fileName, Class<T> clazz) {
return readExcel(fileName, clazz, null);
}
/**
* 取 指定格式的 excel文档
* 注意一旦传入自定义监听器,则返回的list为空,数据需要在自定义监听器里面获取
*
* @param fileName 文件名
* @param clazz 数据类型的class对象
* @param readListener 自定义监听器
* @param 数据类型
* @return
*/
public static <T> List<T> readExcel(String fileName, Class<T> clazz, ReadListener<T> readListener) {
try(FileInputStream fis = new FileInputStream(fileName)) {
return read(fis,clazz,readListener);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* 导出 一个 excel
* 导出excel所有数据
* @param response
* @param fileName 件名 最好为英文,不要后缀名
* @param sheetName sheet名
* @param data 要写出的数据
* @param clazz 要写出数据类的Class类型对象
* @param 要写出的数据类型
*/
public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) {
export(response, null, fileName, sheetName, data, clazz);
}
/**
* 按照指定的属性名进行写出 一个 excel
*
* @param response
* @param attrName 指定的属性名 必须与数据类型的属性名一致
* @param fileName 文件名 最好为英文,不要后缀名
* @param sheetName sheet名
* @param data 要写出的数据
* @param clazz 要写出数据类的Class类型对象
* @param 要写出的数据类型
*/
public static <T> void export(HttpServletResponse response, Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
fileName = StringUtils.isBlank(fileName) ? "student-system-manager" : fileName;
sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setCharacterEncoding("utf-8");
response.addHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());
try(OutputStream os = response.getOutputStream()) {
write(os,attrName,sheetName,data,clazz);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 接收一个excel文件,并且进行解析
* 注意一旦传入自定义监听器,则返回的list为空,数据需要在自定义监听器里面获取
* @param multipartFile excel文件
* @param clazz 数据类型的class对象
* @param readListener 监听器
* @param
* @return
*/
public static <T> List<T> importExcel(MultipartFile multipartFile,Class<T> clazz,ReadListener<T> readListener) {
try(InputStream inputStream = multipartFile.getInputStream()) {
return read(inputStream,clazz,readListener);
} catch (IOException e) {
e.printStackTrace();
}
}
private static <T> void write(OutputStream os, Set<String> attrName, String sheetName, List<T> data, Class<T> clazz) {
ExcelWriterBuilder write = EasyExcel.write(os, clazz);
// 如果没有指定要写出那些属性数据,则写出全部
if (!CollectionUtils.isEmpty(attrName)) {
write.includeColumnFiledNames(attrName);
}
write.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(data);
}
private static <T> List<T> read(InputStream in,Class<T> clazz, ReadListener<T> readListener) {
List<T> list = new ArrayList<>();
Optional<ReadListener> optional = Optional.ofNullable(readListener);
EasyExcel.read(in, clazz, optional.orElse(new AnalysisEventListener<T>() {
@Override
public void invoke(T data, AnalysisContext context) {
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("解析完成");
}
})).sheet().doRead();
return list;
}
}
本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨
希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●’◡’●)
如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。
你在被打击时,记起你的珍贵,抵抗恶意;
你在迷茫时,坚信你的珍贵,抛开蜚语;
爱你所爱 行你所行 听从你心 无问东西