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

简介: 该文章主要介绍了在Java应用中如何使用EasyExcel技术完成对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,进入大厂,上岸

相关文章
|
22天前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
32 6
|
13天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
20 0
|
15天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
31 8
|
13天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
17天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
45 5
|
15天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
15天前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
20天前
|
算法 Java 开发者
Java内存管理与垃圾回收机制深度剖析####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,特别是其垃圾回收机制的工作原理、算法及实践优化策略。不同于传统的摘要概述,本文将以一个虚拟的“城市环卫系统”为比喻,生动形象地揭示Java内存管理的奥秘,旨在帮助开发者更好地理解并调优Java应用的性能。 ####
|
21天前
|
Java
java内存区域
1)栈内存:保存所有的对象名称 2)堆内存:保存每个对象的具体属性 3)全局数据区:保存static类型的属性 4)全局代码区:保存所有的方法定义
21 1
|
12天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
26 0