JAVA解析Excel工具EasyExcel

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便.

1.jar包 

<!-- Excel模板导出依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

2.实体

@Data
//@ContentRowHeight(50) // 设置 Cell 高度 为50
//@HeadRowHeight(40)    //  设置表头 高度 为 40
public class Student {
    /**
     * value: 表头名称
     * index: 列的号, 0表示第一列
     */
    //@ColumnWidth(40) // 设置 Cell 宽度
    @ExcelProperty(value = "学生姓名",index = 0)
    private String name;
    @ExcelProperty(value = "学生年龄",index = 1)
    private String age;
    @ExcelProperty(value = "性别",index = 2)
    private String nameGender;
    @ExcelProperty(value = "修改时间",index = 4)
    private Date updateTime;

    @ExcelIgnore  //忽略这个字段
    private List<SubjectPersonnel> subjectPersonnels;
}

3.导出代码实现(controller )

@GetMapping("exportExcel")
    public void exportExcel(HttpServletResponse response) throws Exception {
        //1.获取要导出的数据
        List<Student> data =studentService.queryDataAll();
        //下载到浏览器默认地址
        ServletOutputStream out = response.getOutputStream();



        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = simpleDateFormat.format(new Date());
        //导出文件的名字
         String fileName=new String((("学生")+date).getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)+".xlsx";

        //添加响应头信息,swagger会有乱码,正常的web不会有 
        response.setHeader("Content-disposition", "attachment; filename=" + fileName);
        response.setContentType("application/msexcel;charset=UTF-8");//设置类型
        response.setHeader("Pragma", "No-cache");//设置头
        response.setHeader("Cache-Control", "no-cache");//设置头
        response.setDateHeader("Expires", 0);//设置日期头

        //第一个参数是路径,第二个是参数实体类的class
        EasyExcel.write(out, Student.class).sheet("学生").doWrite(data);
    }

 4.读取excel(实现监听器)

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.util.ConverterUtils;
import com.sn.execel.dao.StudentDao;
import com.sn.execel.entity.Student;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @program: execel
 * @description: 监听器类中处理读取的数据
 * @author: shenning
 * @create: 2020-08-20 10:34
 */
//有个很重要的点 ExcelListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class ExcelListener extends AnalysisEventListener<Student> {

    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;

    /**
     * 这个集合用于接收 读取Excel文件得到的数据
     */
    private List<Student> studentList = new ArrayList<Student>();

    private StudentDao studentDao;


    public ExcelListener() { }
    /**
     *
     * 不要使用自动装配
     * 在测试类中将dao当参数传进来
     */
    public ExcelListener(StudentDao studentDao) {
        this.studentDao = studentDao;
    }


    /**
     * 加上存储数据库
     */
    private void saveData() {
        //在这个地方可以调用dao
        for(int i=0,j=studentList.size();i<j;i++){
            studentDao.insertOne(studentList.get(i));
        }
    }
    /**
     * 一行一行的读取(Excel第一行不会读取)
     * @param data
     * @param context
     */
    @Override
    public void invoke(Student data, AnalysisContext context) {
        //数据存储到list,供批量处理,或后续自己业务逻辑处理。
        studentList.add(data);
        if(studentList.size()>=BATCH_COUNT){
            saveData();
            //存储完成清理 list
            studentList.clear();
        }
    }



    /**
     * 读取完成之后做的事情,都会来调用
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //这里也要保存数据,确保最后遗留的数据也存储到数据库
        if(studentList.size()>0){
            saveData();
            //存储完成清理 list
            studentList.clear();
        }
    }

    /**
     * 读取Excel 表头
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {
        invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context), context);
    }
}

5.controller

//我这就是dao层,应该会service层
    @Autowired
    private StudentDao studentDao;

    /**
     * 导入
     * @param file
     * @throws Exception
     */
    @PostMapping("readExcel")
    public void readExcel(MultipartFile file) throws Exception {
        //方法上传文件
        InputStream inputStream = file.getInputStream();
        //实例化实现了AnalysisEventListener接口的类
        ExcelListener excelListener = new ExcelListener(studentDao);
        /**
         * 参数1 要读取的文件
         * 参数2 要读取的数据对应的实体类类对象
         * 参数3 监听器对象 可以在创建的时候把dao当做参数传进去
         */
        EasyExcel.read(inputStream,Student.class,excelListener).sheet().doRead();
    }