1 Star 2 Fork 1

咸鱼中二梦 / EasyExcel学习笔记

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

EasyExcel

一、初始EasyExcel

1、Apache POI

先说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. 功能强大
  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多一点)

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

  • 特点

    1. 在数据模型层面进行了封装,使用简单
    2. 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
    3. 只能操作Excel
    4. 不能读取图片
    5. 使用程序读写excel,数据在excel文件、程序<实体类、Map>这两个载体中间相互流转。

二、快速入门--QuickStart

1、导入依赖

 <dependencies>
        <!-- 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>
    </dependencies>

2、最简单的读

2.1 准备工作

需求:单实体导入。

导入Excel学员信息到系统。

包含如下列:姓名、性别、出生日期。

模板详见:excel.xls。

2.1 数据实体类

package model;

import lombok.Data;

import java.util.Date;

/**
 * 学生实体类
 */


@Data
public class Student {
    
    private String name;        // 学生姓名

    private String gender;      // 学生性别
        
    private Date birthday;      // 学生年龄
    
    private String id;          // 学生ID
}

2.3 读取Excel文件

调用EasyExcelAPI读取的Excel文件的测试类ExcelTest

package com.xydream.easyexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import easyexcel.EasyExcelListener;
import model.Student;
import org.junit.Test;

public class ExcelTest {

    @Test
    public void test01() {
        /**
         * 构建一个读的工作牌对象
         *
         * @param pathName  要读的文件的路径
         * @param head      文件中每一行数据要储存的实体的类型的class
         * @param readListener  读监听器,每读一行内容,都会调用一次该对象的invoke,在invoke可以操作使用读取到的数据
         * @return Excel reader builder
         */
        // 获得一个工作簿对象
        ExcelReaderBuilder readWorkBook = EasyExcel.read("easyExcel.xlsx", Student.class, new EasyExcelListener());
        // 获得一个工作表对象
        ExcelReaderSheetBuilder sheet = readWorkBook.sheet();
        // 读取工作表中的内容
        sheet.doRead();
    }

}

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

package easyexcel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import model.Student;

public class EasyExcelListener  extends AnalysisEventListener<Student> {

    // 每读一样,会调用该invoke方法一次
    public void invoke(Student student, AnalysisContext analysisContext) {
        System.out.println("student" + student);
    }

    // 全部读完之后,会调用该方法
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

3、最简单的写

3.1 准备工作

需求:单实体到处

导出多个学生对象到Excel表格

包含如下列:姓名、性别、出生日期

模板详细:easyExcel-write.xlsx

3.2 数据实体类

package model;

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.Data;

import java.util.Date;

/**
 * 学生实体类
 * @ContentRowHeight  设置内容的行高
 * @HeadRowHeight     设置表头的行高
 */

@Data
public class Student {

    /**
     * @ExcelProperty 设置列表的列头
     *  value 对应Excel表中的列头
     *  index 对应Excel表中的列数,默认-1,建议指定时从0开始
     * @ColumnWidth 设置列的列宽
     */
    @ExcelProperty(value = "学生姓名", index = 1)
    @ColumnWidth(20)
    private String name;        // 学生姓名

    @ExcelProperty(value = "学生性别", index = 3)
    @ColumnWidth(15)
    private String gender;      // 学生性别

    @ExcelProperty(value = "年龄", index = 2)
    @ColumnWidth(20)
    private Date birthday;      // 学生年龄

    @ExcelProperty(value = "ID", index = 0)
    private String id;          // 学生ID
}

3.3 写入Excel文件

package com.xydream.easyexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import easyexcel.EasyExcelListener;
import model.Student;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class ExcelTest {

    /**
     * 写入Excel
     */
    @Test
    public void test() {
        /**
         * 构建一个读的工作牌对象
         *
         * @param pathName  要读的文件的路径
         * @param head      文件中每一行数据要储存的实体的类型的class
         * @param readListener  读监听器,每读一行内容,都会调用一次该对象的invoke,在invoke可以操作使用读取到的数据
         * @return Excel reader builder
         */
        // 获得一个工作簿对象
         ExcelWriterBuilder writeWorkBook = EasyExcel.write("easyExcel-write.xlsx", Student.class);
        // 获得一个工作表对象
        ExcelWriterSheetBuilder sheet = writeWorkBook.sheet();
        // 准备数据
         List<Student> students = initDate();
        // 读取工作表中的内容
        sheet.doWrite(students);
    }
 
    /**
     * 初始化数据
     * @return
     */
    private static List<Student> initDate() {
        ArrayList<Student> students = new ArrayList<Student>();
        Student data = new Student();
        for (int i = 0; i < 10; i++) {
            data.setName("学号000" + 1);
            data.setGender("男");
            data.setBirthday(new Date());;
            students.add(data);
        }
        return students;
    }

}

4、API常用注解

4.1 @ExcelProperty注解

标注在成员变量上,加上该注解可以让该成员变量参与读写,同时可以设置该字段的列头属性。value 对应Excel表中的列头,如果不指定则使用成员变量的名字作为列头。index对应Excel表中的列数,默认-1,建议指定时从0开始,如过不指定则根据成员变量位置排序。

4.2 @ColumnWidth注解

标注在成员变量上,加上该注解可以设置该字段的列宽

4.3 @ExcelIgnore注解

标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

4.4 @DateTimeFormat注解

标注在成员变量上,容器转换,代码中String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.test.SimpleDateFormat

4.5 @NumberFormat注解

标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat

4.6 @ContentRowHeight注解

标注在类上,主要设置Excel中内容的行高

4.7 @HeadRowHeight注解

标注在类上,主要设置Excel中表头的行高

4.8 @ExcelIgnoreUnannotated注解

标注在类上,不标注该注解时,默认类中的所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty注解

5、读取时通用参数

ReadWorkBook,ReadSheet都会有的参数,如果为空,默认使用上级。

  • converter转换器,默认加载了很多转换器,也可以自定义。
  • readListener 监听器,在读取数据的过程中会不断的调用监听器。
  • headRowNumber 指定需要读表格列头行数。默认有一行头,也就是认为第二行开始起为数据。
  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class,就是文件中每一行数据对应的代码中的实体类型。
  • headclazz二选一。读取文件头对应的class,也可以使用注解。如歌两个都不指定,则会读取全部数据。
  • autoTrim字符串,表头等数据自动trim(去空格)。
  • password读的审核是否需要使用密码。

6、ReadWorkBook(工作簿对象)参数

  • excelType当前excel的类型,读取时会自动判断,无需设置。
  • inputStreamfile二选一。建议使用file。
  • fileinputStream二选一。读取文件的文件。
  • autoCloseStream自动关闭流。
  • readCache默认小鱼5M用内存,超过5M会使用EhCache,不介意使用这个参数。

7、ReadSheet(工作表对象)参数

  • sheetNo需要读取Sheet的编号,建议使用这个来指定读取哪个Sheet。
  • sheetName根据名字去匹配Sheet,excel 2003不支持根据名字去匹配。

三、实战案例

1、填充简单模板并导出

1、在pom.xml中导入maven依赖

<!-- EasyExcel依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>

2、在Resouces目录下添加模板文件

模板目录:

![image-20220830163424490](C:\Users\1\Pictures\Camera Roll\easyExcel\填充简单模板并导出1.png)

模板内容:

![image-20220830163608873](C:\Users\1\Pictures\Camera Roll\easyExcel\填充简单模板并导出2.png)

3、Controller层代码

/**
 * 最简单的模板填充并导出
 * @param response
 * @param user
 */
@RequestMapping("downExcel")
public void downExcel(HttpServletResponse response, User user) throws Exception {
    // 获取数据
    List<User> list = userService.getUserList();
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    // 获取模板地址
    InputStream templateFileName  = Thread.currentThread().getContextClassLoader().getResourceAsStream("files/模板.xlsx");
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(templateFileName).build();
    // 创建一个sheet页
    WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
    // 加入数据
    excelWriter.fill(list,writeSheet);
    excelWriter.finish();
}

4、User实体类

import lombok.Data;

@Data
public class User {
    private String id;
    private String username;
    private String address;
}

5、Service层【这里直接省略了Service接口】

@Service
public class UserService {
    public List<User> getUserList() {
    	return userDao.getUserList();
	}
}

6、dao接口【通过mybatis注解直接查询数据库, 该方法需要在启动类中添加@MapperScan注解扫描UserDao】

public interface UserDao {
    @Select("select * from tb_user")
    List<User> getUserList();
}

7、启动类

@MapperScan("com.xydream.user.dao")
@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

2、填充复杂模板并导出

1、在pom.xml中导入maven依赖

<!-- EasyExcel依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>

2、在Resouces目录下添加模板文件

模板目录:

![image-20220830163424490](C:\Users\1\Pictures\Camera Roll\easyExcel\填充简单模板并导出1.png)

模板内容:

![填充复杂模板并导出1](C:\Users\1\Pictures\Camera Roll\easyExcel\填充复杂模板并导出1.png)

3、Controller层代码

/**
 * 填充复杂模板并导出
 * @param response
 * @param user
 */
@RequestMapping("downExcel")
public void downExcel(HttpServletResponse response, User user) throws Exception {
    // 获取列表数据
    List<User> list = userService.getUserList();
    // 获取用户列表统计数据
    HashMap<String, Object> map = userService.getUserCount(list);
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    // 获取模板地址
    InputStream templateFileName  = Thread.currentThread().getContextClassLoader().getResourceAsStream("files/模板.xlsx");
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(templateFileName).build();
    // 创建一个sheet页
    WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
    // 加入用户列表数据
    excelWriter.fill(list, writeSheet);
    // 加入用户列表统计数据
    excelWriter.fill(map, writeSheet);
    excelWriter.finish();
}

4、User实体类

import lombok.Data;

@Data
public class User {
    private String id;
    private String username;
    private String address;
}

5、Service层【这里直接省略了Service接口】

@Service
public class UserService {
    public List<User> getUserList() {
    	return userDao.getUserList();
	}
    
    public HashMap<String, Object> getUserCount(List<User> list) {
        HashMap<String, Object> map = new HashMap<>();
        if (list != null && list.size() > 0) {
            map.put("userCount", list.size());
        } else {
            map.put("userCount", 0);
        }
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String date = simpleDateFormat.format(new Date());
        map.put("date", date);
        return map;
    }
}

6、dao接口【通过mybatis注解直接查询数据库, 该方法需要在启动类中添加@MapperScan注解扫描UserDao】

public interface UserDao {
    @Select("select * from tb_user")
    List<User> getUserList();
}

7、启动类

@MapperScan("com.xydream.user.dao")
@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

空文件

简介

根据B站黑马程序员练习EasyExcel的相关代码与文档 展开 收起
Java
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/XianYuZhonErMeng/easy-excel-exercise.git
git@gitee.com:XianYuZhonErMeng/easy-excel-exercise.git
XianYuZhonErMeng
easy-excel-exercise
EasyExcel学习笔记
master

搜索帮助