大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel

前言

我们在开发应用系统的时候经常遇到操作或解析 Excel 的需求,我们在实现此功能的时候也都是借助 Apach POI 去操作 Excel,但是使用过这个框架的小伙伴都知道,这个框架并不是很好用,比如使用步骤繁琐、动态写出Excel操作非常麻烦、读写时需要占用较大的内容,而且对新手很不友好,上手比较难,这就很让人头疼了…

那么今天就给大家推荐另一种操作 Excel 的框架 —— EasyExcel - 用更简单的方式操作 Excel。

EasyExcel 框架的简介

在讲解如何使用 EasyExcel 框架之前,我们先简单的介绍一下 EasyExcel 框架:

EasyExcel 是一个基于Java 的简单、省内存的读写 Excel 的开源框架。它可以在尽可能节约内存的情况下支持读写百兆的 Excel 文件。

EasyExcel 是阿里巴巴开源的一个 Excel 处理框架,以使用简单、节省内存著称,那么它到底有多省内存呢~ 我们接着往下看

大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel_第1张图片
它可以在1分钟内读取75M(46W行25列)的 Excel 文件,而且仅占用64M内存(这要是用 Apach POI 去读取同样大小文件,那花费的时间可就不仅仅是一分钟的事了)。那它为什么可以在占用少量内存的前提下做到这么迅速呢?
我们通过百度也找到了答案:EasyExcel 在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个进行解析,并将每一行的解析结果以观察者模式通知处理(AnalysisEventListener),这也就是为什么 EasyExcel 在操作 Excel 文件时能大大减少内存的占用。

咱们已经对 EasyExcel 框架有了一个简单的了解,那么下面就该上重点了,看看我们如何使用 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 框架的使用——简单写

接下来我们就通过 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 文件,如下所示

大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel_第2张图片

接下来我们对 EasyExcel.write 方法做一个简单的解释:EasyExcel.write 方法表示构建一个Excel写对象,该方法包含了两个参数,第一个参数代表生成的文件名,第二个参数代表写到表格中的数据类型的 Class 对象。我们一起看一下 EasyExcel.write 方法的源码:

大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel_第3张图片
通过源码我们可以看到,在构建 Excel 写对象的时候可以通过多种方式构建,具体使用哪种方式还要看具体的需求。

P.S. 眼尖的小伙伴应该看到了示例代码中的.sheet(),sheet() 代表的是在Excel 文件中的哪个 sheet 页写入数据,如果不指定,默认在第一个。

EasyExcel 框架的使用——复杂写

我们通过上面的截图可以看到,写出的数据表头均为实体类的属性名,且列的顺序也是按照类中属性的顺序进行排列的,但是在实际开发过程中,表头均为自定义信息,且顺序也不一定按照属性的顺序来。因此我们需要对学生类做一点小小的修改

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 表格 ✌

大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel_第4张图片

EasyExcel 框架的使用——简单读

咱们说完了写操作,接下来再来看看 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 中的数据有没有被读取出来~
大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel_第5张图片
还是老规矩,我们对 EasyExcel.write 方法做一个简单的解释:EasyExcel.read 方法用来创建 ExcelReaderBuilder 对象,我们通过 ExcelReaderBuilder 对 Excel 文档进行解析。EasyExcel.read 方法需要传入三个参数:

  • 第一个参数:待解析文件的路径,除了传入一个文件路径以外,还可以传入 InputStream 流(源码如下图所示)

大聪明教你学Java | EasyExcel - 用更简单的方式操作Excel_第6张图片

  • 第二个参数:数据类型的Class类型对象,通过上图中的源码我们可以知道,这个参数是非必传参数
  • 第三个参数:代表了事件监听器,在文章开头介绍 EasyExcel 框架时说过,该框架是基于SAX的一种解析,加载一行数据到内存就会去解析一行(主要是为了节约内存)。在 EasyExcelDemo 类中有两个方法,分别是 invoke() 方法和 doAfterAllAnalysed() 方法,前者在每解析完一行就会调用一次,其中 data 数据表示解析出来的一行 Excel 数据;而后者则是待所有数据解析完毕以后才会去调用的方法。

P.S. 读中的 sheet() 方法和写中的 sheet() 方法含义是相同的,都代表了要操作 Excel 文件中的第几个 sheet。

EasyExcel 框架的使用——其他

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

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●’◡’●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

你在被打击时,记起你的珍贵,抵抗恶意;
你在迷茫时,坚信你的珍贵,抛开蜚语;
爱你所爱 行你所行 听从你心 无问东西

你可能感兴趣的:(独乐乐不如众乐乐,java,开发语言,后端,EasyExcel)