阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel

课程介绍

本博客主要讲解在java应用中如何利用EasyExcel技术完成对excel文件的导入和导出操作;

黑马阿里EasyExcel实战教程,阿里开源技术实现MySQL和Excel之间海量数据处理_哔哩哔哩_bilibili

技术要求

  1. java基础及web基础

  2. SSM(SpringMVC+Spring+Mybatis)

  3. mysql数据库

基本概念

1. EasyExcel是什么

EasyExcel是一个基于Java的简单、省内存的读写Excel的阿里开源项目。

在尽可能节约内存的情况下支持读写百M的Excel。

2. EasyExcel 能用在哪里

项目中涉及到Excel文件,CSV文件大多数的读写操作,均可以使用!

3. 为什么要选用EasyExcel解析excel

4. 如何使用

阿里官方文档: EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel

快速入门

1:环境搭建:

1)创建maven工程

2)引入相关坐标

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.2</version>
</dependency> 

3)参考官方API完成功能

2: 简单写excel

获取代码路径的工具类,可以获取到模块这一级的磁盘路径

package com.ithiema.utils;
/*
    获取代码路径的工具类,可以获取到模块这一级的磁盘路径;
 */
public class TestFileUtil {
    public static String getPath() {
        return TestFileUtil.class.getResource("/").getPath().replace("classes/","");
    }

    public static void main(String[] args) {
        System.out.println(getPath());
    }
}

1)编写模型类并加入注解

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    // 成员变量
    @ExcelProperty("员工工号")
    private int id;
    @ExcelProperty("员工姓名")
    private String name;
    @ExcelProperty("员工工资")
    private double salary;
    @ExcelProperty("入职日期")
    private Date date;
}

2)编写获取测试数据的方法

3)调用官方API完成写功能

不知道为什么 我测试的时候,路径一直识别不到,报错

com.alibaba.excel.exception.ExcelGenerateException: Can not found file.

    at com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder.<init>(WriteWorkbookHolder.java:167)
    at com.alibaba.excel.context.WriteContextImpl.initCurrentWorkbookHolder(WriteContextImpl.java:107)
    at com.alibaba.excel.context.WriteContextImpl.<init>(WriteContextImpl.java:90)
    at com.alibaba.excel.write.ExcelBuilderImpl.<init>(ExcelBuilderImpl.java:36)
    at com.alibaba.excel.ExcelWriter.<init>(ExcelWriter.java:36)
    at com.alibaba.excel.write.builder.ExcelWriterBuilder.build(ExcelWriterBuilder.java:114)
    at com.alibaba.excel.write.builder.ExcelWriterBuilder.sheet(ExcelWriterBuilder.java:130)
    at com.alibaba.excel.write.builder.ExcelWriterBuilder.sheet(ExcelWriterBuilder.java:126)
    at com.ithiema.write.SimpleWrite.write(SimpleWrite.java:30)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

路径改成这种才行 String fileName = "D:\\MLdata\\1predict.xlsx";

/*
    练习easyexcel的简单写数据
 */
public class SimpleWrite {

    @Test
    public void testWrite(){
        String name = TestFileUtil.getPath() + "简单写数据" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可        
        EasyExcel.write(name, Employee.class).sheet("我的快速入门").doWrite(data(100000));
    }

    // 准备测试数据的方法
    private List<Employee> data(int i) {
        List<Employee> list = new ArrayList<>();
        for (int j = 1; j <= i; j++) {
            list.add(new Employee(j,"测试数据"+j,6.6*j,new Date()));
        }
        return list;
    }
}

核心代码:

EasyExcel.write(fileName, DemoData.class).sheet("测试").doWrite(data(100000));

3: 简单读excel

1)编写模型类并加入注解

还是上面的Employee模型

2)监听器介绍

PageReadListener 阿里自定义的监听器

3)调用官方API完成写功能

package com.ithiema.read;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.ithiema.pojo.Employee;
import com.ithiema.utils.TestFileUtil;
import org.junit.Test;

import java.io.File;

/*
    快速入门读数据
 */
public class SimpleReader {
    @Test
    public void read(){
        //JDK8+,自定义监听器 since:3.0.0-beta1
        String fileName = TestFileUtil.getPath() + "simpleWrite1669290881801.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        // 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
        EasyExcel.read(fileName, Employee.class, new PageReadListener<Employee>(dataList -> {
            for (Employee demoData : dataList) {
                System.out.println(demoData);
            }
        })).sheet().doRead();
    }
}

核心代码:

EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>
(list -> System.out.println(list))).sheet().doRead();

进阶操作

1: 批量(重复)写数据

1)编写模型类并加入注解

2)调用官方API完成写功能

package com.ithiema.write;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.ithiema.pojo.Employee;
import com.ithiema.utils.TestFileUtil;
import org.junit.Test;

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

/*
    批量写数据
 */
public class ManyWrite {
    // 准备测试数据的方法
    private List<Employee> data(int count) {
        List<Employee> list = ListUtils.newArrayList();
        for (int i = 1; i <= count; i++) {
            list.add(new Employee(i,"测试数据"+i,new Date(),6.6*i));
        }
        return list;
    }

    // 批量写数据  100万
    @Test
    public void write(){
        // 方法2: 如果写到不同的sheet 同一个对象
        String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            WriteSheet writeSheet = EasyExcel.writerSheet("测试数据").build();
            long t1 = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<Employee> data = data(10000);
                excelWriter.write(data, writeSheet);
            }
            long t2 = System.currentTimeMillis();
            System.out.println(t2-t1);
        }
    }
}

try (ExcelWriter writer = EasyExcel.write(fileName, DemoData.class).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("测试表1").build();
            for (int i = 0; i < 3; i++) {
                ExcelWriter write = writer.write(data(100000), writeSheet);
            }
        }

3) 上面写的数据格式不好看怎么办?

利用阿里提供的模版写数据,模版设置的样式新添加的数据会自动包含样式

2: 按模版填充单个对象数据

1)编写模型类并加入注解

2)按要求编写模版文件

3)调用官方API完成写功能

/* 练习填充数据
 */
public class FillWriter {
    // 准备测试数据的方法
    private List<Employee> data(int count) {
        List<Employee> list = ListUtils.newArrayList();
        for (int i = 1; i <= count; i++) {
            list.add(new Employee(i,"测试数据"+i,new Date(),6.6*i));
        }
        return list;
    }

    // 批量写数据  100万
    @Test
    public void write(){
        // 方案3 分多次 填充 会使用文件缓存(省内存)
        String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
        String templateFileName = TestFileUtil.getPath() + "模版.xlsx";
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            long t1 = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                excelWriter.fill(data(10000), writeSheet);
            }
            long t2 = System.currentTimeMillis();
            System.out.println(t2-t1);
        }
    }
}
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);

3: 按模版批量填充多个对象数据

1)编写模型类并加入注解

2)按要求编写模版文件

3)调用官方API完成写功能

try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
            WriteSheet writeSheet2 = EasyExcel.writerSheet("数据2").build();
            excelWriter.fill(data(300000), writeSheet);
            excelWriter.fill(data(300000), writeSheet2);
        }

4: 自定义监听器读海量(百万级别)数据并监控内存消耗

1)编写模型类并加入注解

2)自定义监听器

/*    自定义监听器读数据
 */
public class EmployeeListener implements ReadListener<Employee> {

    private int count = 100;
    private ArrayList<Employee> list = new ArrayList<>(count);
    private EmployeeDao dao;

    public EmployeeListener(EmployeeDao dao) {
        this.dao = dao;
    }

    // 每读一行数据,都会调用这个方法
    @Override
    public void invoke(Employee employee, AnalysisContext analysisContext) {
        // 将读取到的一行数据添加到集合
        list.add(employee);
        // 判断是不是到达缓存量了
        if(list.size()>=100){
            // 模拟操作数据库
            dao.save(list);
            list= new ArrayList<>(count);
        }
    }

    // 读完整个excel之后,会调用这个方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        if(list.size()>0){
            // 操作数据库
            dao.save(list);
            list= new ArrayList<>(count);
        }
    }
}

3)调用官方API完成写功能

/*    自定义监听器,读海量数据
 */
public class ManyRead {
    @Test
    public void read(){
        String fileName = TestFileUtil.getPath()+"repeatedWrite1669291976389.xlsx";
        ExcelReader reader = EasyExcel.read(fileName, Employee.class, 
            new EmployeeListener(new EmployeeDao())).build();
        ReadSheet sheet = EasyExcel.readSheet().build();
        reader.read(sheet);
    }
}

EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();

综合应用

1: 环境搭建

2: 实现文件导入功能(采用异步上传并使用遮罩层)

   @RequestMapping("/upload")
    @ResponseBody
    public void upload(MultipartFile file, HttpServletResponse response) throws IOException {
        long t1 = System.currentTimeMillis();
        EasyExcel.read(file.getInputStream(), Employee.class, new EmployeeListener(service)).sheet().doRead();
        long t2 = System.currentTimeMillis();
       response.setContentType("text/html;charset=utf-8");//提示给前端
       response.getWriter().print("导入数据成功,共用时:"+(t2-t1));
    }

3: 实现文件导出功能(采用同步下载并解决文件名中文乱码)

@RequestMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试666", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), Employee.class).sheet("模板").doWrite(service.getData());
    }

非常感谢您阅读到这里,创作不易!如果这篇文章对您有帮助,希望能留下您的点赞 👍 关注 💖 收藏 💕 评论 💬 感谢支持!!!

听说 三连能够给人 带来好运!更有可能年入百w,进入大厂,上岸

[ 本文作者 ]   软工菜鸡
[ 博客链接 ]   https://blog.csdn.net/m0_67184231
[ 版权声明 ]   如果您在非 CSDN 网站内看到这一行,
说明该死的侵权网络爬虫可能在本人还没有完整发布的时候就抓走了我的文章,
可能导致内容不完整,请去上述的原文链接查看原文。
相关推荐
南宫生8 分钟前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石16 分钟前
12/21java基础
java
李小白6624 分钟前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp37 分钟前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗1 小时前
常用类晨考day15
java
骇客野人1 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
yuanbenshidiaos2 小时前
c++---------数据类型
java·jvm·c++
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro3 小时前
【Spring】Spring框架之-AOP
java·mysql·spring