EasyExcel使用与详细说明,EasyExcel工具类(一)

简介: EasyExcel使用与详细说明,EasyExcel工具类

1.Apache POI

Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。但是存在如下一些问题:

1.1 学习使用成本较高

对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。

想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。

POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。

1.2 POI的内存消耗较大

大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。

总体上来说,简单写法重度依赖内存,复杂写法学习成本高。

1.3 特点

  1. 功能强大
  2. 代码书写冗余繁杂
  3. 读写大文件耗费内存较大,容易OOM

2. 初识EasyExcel

2.1 重写了POI对07版Excel的解析

  • EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。
  • 下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)

  • 在上层做了模型转换的封装,让使用者更加简单方便

2.2 特点

  1. 在数据模型层面进行了封装,使用简单
  2. 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
  3. 只能操作Excel
  4. 不能读取图片

3.快速入门

3.1 导入依赖坐标

<!-- EasyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.6</version>
</dependency>
<!-- lombok 优雅编程 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>
<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

3.2 最简单的读

3.2.1 需求、准备工作

/**
 * 需求:单实体导入
 * 导入Excel学员信息到系统。
 * 包含如下列:姓名、出生日期、性别
 * 模板:逐浪教育学员信息表.xls文件
 */

3.2.2 编写导出数据的实体

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
 * 学生实体类
 * lombok:通过一个插件 + 一个依赖 ,就可以在编译的时候自动帮助生成实体类常用方法
 *
 * @author 狐狸半面添
 * @create 2023-02-26 14:56
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    /**
     * 学生姓名
     */
    private String name;
    /**
     * 学生出生日期
     */
    private Date birthday;
    /**
     * 学生性别
     */
    private String gender;
    /**
     * id
     */
    private String id;
}

3.2.3 读取Excel的监听器,用于处理读取产生的数据

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.fox.easyexcel.domain.Student;
/**
 * 读取文档的监听器类
 *
 * @author 狐狸半面添
 * @create 2023-02-26 15:10
 */
public class StudentListener extends AnalysisEventListener<Student> {
    /**
     * 读监听器,每读一行内容,都会调用一次invoke,在invoke可以操作使用读取到的数据
     *
     * @param student 每次读取到的数据封装的对象
     * @param analysisContext
     */
    public void invoke(Student student, AnalysisContext analysisContext) {
        System.out.println(student);
    }
    /**
     * 读取完整个文档之后,调用的方法
     *
     * @param analysisContext
     */
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // todo
    }
}

3.2.4 读取Excel文件

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.listener.StudentListener;
import org.junit.Test;
/**
 * @author 狐狸半面添
 * @create 2023-02-26 15:03
 */
public class ExcelTest {
    /**
     * 工作簿:一个excel文件就是一个工作簿
     * 工作表:一个工作簿可以有多个工作表(sheet)
     */
    @Test
    public void test01() {
        /*
            1.获得一个工作簿对象
            构建一个读的工作簿对象,参数说明:
                - pathName:要读的文件的路径
                - head:文件中每一行数据要存储到的实体的类型的class
                - readListener:读监听器,每读一行内容,都会调用一次该对象的invoke,在invoke可以操作使用读取到的数据
         */
        ExcelReaderBuilder readWorkBook = EasyExcel.read("逐浪教育学员信息表.xlsx", Student.class, new StudentListener());
        // 2.获得一个工作表对象,默认读取第一个工作表
        ExcelReaderSheetBuilder sheet = readWorkBook.sheet();
        // 3.读取工作表中的内容
        sheet.doRead();
    }
}

3.3 最简单的写

3.3.1 需求、准备工作

/**
 * 需求:单实体导出
 * 导出多个学生对象到Excel表格
 * 包含如下列:姓名、出生日期、性别
 * 模板详见:逐浪教育学员信息表.xlsx
 */

3.3.2 编写导出数据的实体

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
 * 学生实体类
 * lombok:通过一个插件 + 一个依赖 ,就可以在编译的时候自动帮助生成实体类常用方法
 * 注解 @ContentRowHeight():内容的行高
 * 注解 @HeadRowHeight:表头的行高
 *
 * @author 狐狸半面添
 * @create 2023-02-26 14:56
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    /**
     * 学生姓名
     */
    @ExcelProperty("学生姓名")
    @ColumnWidth(20)
    private String name;
    /**
     * 学生出生日期
     */
    @ExcelProperty("出生日期")
    @ColumnWidth(20)
    private Date birthday;
    /**
     * index 从0开始
     * 学生性别
     */
    @ExcelProperty(value = "学生性别", index = 1)
    private String gender;
    /**
     * id
     */
    @ExcelIgnore
    private String id;
}

3.3.3 准备数据并写入到文件

@Test
    public void test02() {
        /*
            1.构建一个写的工作簿对象
                - pathName:要写入的文件路径
                - head:封装写入的数据的实体类型
         */
        ExcelWriterBuilder writeWorkBook = EasyExcel.write("逐浪教育学员信息表.xlsx", Student.class);
        // 2.获取工作表对象,默认是第一个工作表
        ExcelWriterSheetBuilder sheet = writeWorkBook.sheet();
        // 3.生成十个测试对象
        ArrayList<Student> students = new ArrayList<Student>();
        for (int i = 1; i <= 10; i++) {
            students.add(new Student("逐浪者-" + i, new Date(), "男", null));
        }
        // 4.将数据写入工作表
        sheet.doWrite(students);
    }

3.4 文件上传和下载

基于SpringMVC的文件上传和下载

3.4.1 导入依赖

<!-- EasyExcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.1.6</version>
        </dependency>
        <!-- lombok 优雅编程 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

3.4.2 文件上传

🍀 编写excel中每一行对应的实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    /**
     * 学生姓名
     */
    private String name;
    /**
     * 学生性别
     */
    private String gender;
    /**
     * 学生出生日期
     */
    private Date birthday;
    /**
     * id
     */
    private String id;
}

🍀 回调监听器StudentReadListener

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.service.StudentService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
/**
 * 读取文档的监听器类
 *
 * @author 狐狸半面添
 * @create 2023-02-26 15:10
 */
@Component
@Scope("prototype") //作者要求每次读取都要使用新的Listener
public class StudentListener extends AnalysisEventListener<Student> {
    @Resource
    private StudentService studentService;
    public static final ThreadLocal<ArrayList<Student>> threadLocal = new ThreadLocal<ArrayList<Student>>();
    /**
     * 读监听器,每读一行内容,都会调用一次invoke,在invoke可以操作使用读取到的数据
     *
     * @param student         每次读取到的数据封装的对象
     * @param analysisContext
     */
    public void invoke(Student student, AnalysisContext analysisContext) {
        ArrayList<Student> students = threadLocal.get();
        if (students == null) {
            threadLocal.set(new ArrayList<Student>());
            students = threadLocal.get();
        }
        students.add(student);
        if (students.size() == 5) {
            studentService.save(students);
            students.clear();
        }
    }
    /**
     * 读取完整个文档之后,调用的方法
     *
     * @param analysisContext
     */
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // todo
    }
}

🍀 业务代码接口StudentService和实现类StudentServiceImpl

import com.fox.easyexcel.domain.Student;
import java.util.ArrayList;
/**
 * @author 狐狸半面添
 * @create 2023-02-26 16:27
 */
public interface StudentService {
    /**
     * 保存学生信息
     *
     * @param students 信息列表
     */
    void save(ArrayList<Student> students);
}
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.service.StudentService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
/**
 * @author 狐狸半面添
 * @create 2023-02-26 16:27
 */
@Service
public class StudentServiceImpl implements StudentService {
    @Override
    public void save(ArrayList<Student> students) {
        System.out.println("save to database = " + students);
    }
}

🍀 读取上传的Excel文件

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import com.fox.easyexcel.domain.Student;
import com.fox.easyexcel.listener.StudentListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
 * @author 狐狸半面添
 * @create 2023-02-26 16:29
 */
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
    @Resource
    private StudentListener studentListener;
    @PostMapping("/read")
    public String readExcel(MultipartFile uploadExcel) {
        try {
            // 1.获取工作簿
            ExcelReaderBuilder readWorkBook = EasyExcel.read(uploadExcel.getInputStream(), Student.class, studentListener);
            // 2.获取工作表
            ExcelReaderSheetBuilder sheet = readWorkBook.sheet();
            // 3.读取数据
            sheet.doRead();
            // 4.释放线程
            StudentListener.threadLocal.remove();
            return "success";
        } catch (Exception e) {
            log.error("读取文件失败:{}", e.getMessage());
            return "fail";
        }
    }
}

🍀 ApiFox测试


相关文章
|
JSON easyexcel Java
EasyExcel的简单使用
EasyExcel的简单使用,以及如何通过postman进行导入导出功能的调试
1200 1
|
1月前
|
easyexcel 计算机视觉
|
3月前
|
easyexcel Java
EasyExcel的使用
EasyExcel的使用
54 0
|
3月前
|
存储 easyexcel Java
SpringBoot整合Easyexcel操作Excel,闲暇之余,让我们学习更多
SpringBoot整合Easyexcel操作Excel,闲暇之余,让我们学习更多
108 0
|
5月前
|
XML 编解码 算法
Hutool超级工具类
Hutool超级工具类
47 0
|
8月前
|
存储 easyexcel Java
EasyExcel
EasyExcel
246 0
|
8月前
|
前端开发 Java 索引
从零玩转EasyPoi
从零玩转EasyPoi
313 0
|
8月前
|
easyexcel Java API
POI及EasyExcel【Java提高】
POI及EasyExcel【Java提高】
283 0
|
9月前
|
easyexcel API 数据安全/隐私保护
EasyExcel使用与详细说明,EasyExcel工具类(三)
EasyExcel使用与详细说明,EasyExcel工具类
4799 0
|
9月前
|
easyexcel
EasyExcel使用与详细说明,EasyExcel工具类(二)
EasyExcel使用与详细说明,EasyExcel工具类
546 0