当前位置: 首页 > news >正文

Java操作Excel - Easy Excel

一、介绍

官网 https://easyexcel.opensource.alibaba.com/

EasyExcel是阿里巴巴开源的,一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
其特点是:

  • 快速的读取excel中的数据。
  • 映射excel和实体类,让代码变的更加简洁。
  • 在读写大文件的时候使用磁盘做缓存,更加的节约内存。

二、使用EasyExcel写

2.1 导入依赖

<!-- easyexcel依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.1</version>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

2.2 导出表格极简入门

①声明一个简单的对象

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    @ExcelProperty("字符串标题") //这里默认为value属性赋值,value为导出的表格的表头
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    @ExcelIgnore  //忽略这个字段,生成的表格中就不会有该字段
    private String ignore;
}

②写一个简单的表格导出案例

public class SimpleWriteTest {
    @Test
    public void simpleWrite(){
        //1.模拟要写出数据
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        //2.设置写出的文件路径
        String fileName = "demo1.xlsx";
        //3.利用EasyExcel写出,会自动关流
        //write方法的参数1:文件路径,参数2:模板对象类型
        EasyExcel.write(fileName, DemoData.class)
                .sheet("模板")    //sheet页名字
                .doWrite(list);  //要写出的数据
    }
}

这样,就会在当前项目目录下生成名为demo1.xlsx的表格,内容如下:
在这里插入图片描述

2.3 根据参数导出指定属性

案例1:指定哪些属性不需要导出到表格中,仅导出剩余属性。

public class SimpleWriteTest {
    @Test
    public void excludeWrite(){
        //1.模拟要写出数据
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        //2.设置导出过程中剔除的字段
        Set<String> excludeColumnFiledNames = new HashSet<String>();
        excludeColumnFiledNames.add("date");//这里剔除date属性
        //3.设置写出的文件路径
        String fileName = "demo2.xlsx";
        //4.写出
        EasyExcel.write(fileName, DemoData.class)
                .excludeColumnFieldNames(excludeColumnFiledNames) //将要剔除的列设置给EasyExcel
                .sheet("模板")
                .doWrite(list);
    }
}

结果如下,就没有了日期列。
在这里插入图片描述

案例2:指定仅导出哪些属性,剩余属性不导出。

public class SimpleWriteTest {

    @Test
    public void IncludeWrite(){
        //1.模拟要写出数据
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        //2.设置仅导出哪些属性
        Set<String> includeColumnFiledNames = new HashSet<String>();
        includeColumnFiledNames.add("date");
        //3.设置写出的文件路径
        String fileName = "demo3.xlsx";
        //4.写出
        EasyExcel.write(fileName, DemoData.class)
                .includeColumnFieldNames(includeColumnFiledNames) //设置给EasyExcel
                .sheet("模板")
                .doWrite(list);
        
        //这里导出的表格中就只有:时间字段。
    }
}

2.4 指定写入的列

使用@ExcelProperty注解的index属性指定要导入列的下标。

@Getter
@Setter
@EqualsAndHashCode
public class IndexData {
    @ExcelProperty(value = "字符串标题", index = 0)
    private String string;
    @ExcelProperty(value = "日期标题", index = 1)
    private Date date;
    //这里设置3 会导致第三列空的
    @ExcelProperty(value = "数字标题", index = 3)
    private Double doubleData;
}
public class WriteTest {
    @Test
    public void writeDemo(){
        //1.模拟要写出数据
        List<IndexData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            IndexData data = new IndexData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        //2.设置写出的文件路径
        String fileName = "demo4.xlsx";
        //3.写出
        EasyExcel.write(fileName, IndexData.class)
                .sheet("模板")
                .doWrite(list);
    }
}

能看到第三列是空的:
在这里插入图片描述

2.5 复杂头写入

在导出表格的时候,会因为业务需求,导出复杂的表头,咱们可以使用@ExcelProperty注解的value属性来实现这个需求。

例如我们要导出一个如图的表格:
在这里插入图片描述
仅需通过value属性设置即可:

@Getter
@Setter
@EqualsAndHashCode
public class ComplexHeadData {
    @ExcelProperty(value={"主标题", "副标题1","字符串标题"})
    private String string;
    @ExcelProperty(value={"主标题", "副标题1","日期标题"})
    private Date date;
    @ExcelProperty(value={"主标题", "副标题2","数字标题"})
    private Double doubleData;
}

导出的代码:

public class WriteTest {
    @Test
    public void complexHeadWrite() {
        List<ComplexHeadData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            ComplexHeadData data = new ComplexHeadData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        //2.设置写出的文件路径
        String fileName = "demo5.xlsx";
        //3.导出
        EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(list);
    }
}

2.6 写出到多个sheet

2.6.1 多个sheet中写入相同类型对象

public class WriteTest {
    @Test
    public void repeatedWrite() {
        //1.模拟要写出数据
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        String fileName = "demo6.xlsx";
        //2.创建 ExcelWriter对象,并指定文件名和对象类型
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            // 这里模拟5次
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet,这里注意必须指定sheetNo(sheet页编号),而且sheetName(sheet也名字)必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                // 将数据写入到sheet页中,最终,会生成5个sheet页
                excelWriter.write(list, writeSheet);
            }
        }
    }
}

效果见下图:
在这里插入图片描述

2.6.2 多个sheet中写入不同类型对象

public class WriteTest {
    @Test
    public void repeatedWrite1() {
        //1.模拟要写出数据
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        String fileName = "demo7.xlsx";
        //2.创建ExcelWriter对象,仅需指定文件名,因为写入的数据是不固定的,所以不能写死
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
            // 模拟5次
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。
                // 注意:head方法中的DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class,实际上可以一直变
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
                // 写出数据
                excelWriter.write(list, writeSheet);
            }
        }
    }
}

2.7 日期、数字或者自定义格式转换

在写出数据时,如果需要对数据进行格式设置,可以为对象进行如下设置:

@Getter
@Setter
@EqualsAndHashCode
public class ConverterData {
    /**
     * 我想所有的 字符串起前面加上"自定义:"三个字
     */
    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
    private String string;
    /**
     * 我想写到excel 用年月日的格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 我想写到excel 用百分比表示
     */
    @NumberFormat("#.##%")
    @ExcelProperty(value = "数字标题")
    private Double doubleData;
}

自定义格式:

public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    /**
     * 这里读的时候会调用,先不管
     */
    @Override
    public String convertToJavaData(ReadConverterContext<?> context) {
        return "自定义:" + context.getReadCellData().getStringValue();
    }
    /**
     * 这里是写的时候会调用
     * 会在原有内容之前拼接上:"自定义:"
     */
    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
        return new WriteCellData<>("自定义:"+context.getValue());
    }
}

写出的代码:

public class SimpleWriteTest {
    @Test
    public void converterWrite() {
        //1.模拟要写出数据
        List<ConverterData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            ConverterData data = new ConverterData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        String fileName = "demo8.xlsx";
        //3.写出
        EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(list);
    }
}

效果如下:
在这里插入图片描述

2.8 设置列宽,行高

使用注解在对象的属性上进行设置即可

@Getter
@Setter
@EqualsAndHashCode
@ContentRowHeight(20) //内容高度为:20
@HeadRowHeight(40)    //表头高度为:40
@ColumnWidth(25)      //列宽为:25
public class WidthAndHeightData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    //单独设置宽度为50
    @ColumnWidth(50)
    @ExcelProperty("数字标题")
    private Double doubleData;
}

导出代码为:

public class WriteTest {
    @Test
    public void widthAndHeightWrite() {
        //1.模拟要写出数据
        List<WidthAndHeightData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            WidthAndHeightData data = new WidthAndHeightData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        String fileName = "demo9.xlsx";
        //3.写出
        EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(list);
    }
}

2.9 合并单元格

使用注解来设置合并规则:

@Getter
@Setter
@EqualsAndHashCode
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
    // 这一列 每隔2行 合并单元格
    @ContentLoopMerge(eachRow = 2)
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}

写出:

public class SimpleWriteTest {
    @Test
    public void mergeWrite() {
        //1.模拟要写出数据
        List<DemoMergeData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoMergeData data = new DemoMergeData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        String fileName = "demo10.xlsx";
        //2.写出
        EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(list);
    }
}

效果如下
在这里插入图片描述

2.10 在web项目中实现excel的导出

2.10.1 客户端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="/download">导出excel</a>
</body>
</html>

2.10.2 服务端代码

@Controller
public class DownloadController {
    @GetMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        //1.模拟数据
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        //2.执行导出
        //设置响应类型
        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");
        //导出
        EasyExcel.write(response.getOutputStream(), DemoData.class).sheet("模板").doWrite(list);
    }
}

三、使用EasyExcel读

easyexcel读取表格内容是基于监听器方式实现的,我们需要实现它提供的ReadListener接口,进而实现读取的功能。

3.1 极简入门

假如有表格demo.xlsx的内容如下:
在这里插入图片描述
① 首先我们需要准备一个对象与之对应:

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}

② 准备监听器

public class DemoDataListener implements ReadListener<DemoData> {
    /**
     * 每存储100条,可以存储一次数据库,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 用于缓存的数据的集合
     */
    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /* 在实际开发中我们可以使用有参构造,将自己的 dao 或者 service传进来,进而实现数据入库
    private DemoDAO demoDAO;
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
     */

    /**
     * 这个每一条数据解析都会来调用
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        System.out.println("每次读到内容是:"+data);
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            //1.存储数据库
            //demoDAO.saveBatch(cachedDataList);
            //2.存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }
    /**
     * 所有数据解析完成了 都会来调用
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        System.out.println("所有数据解析完成!");
    }
}

③ 读取表格内容

public class ReadTest {
    @Test
    public void simpleRead() {
        String fileName = "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    }
}

3.2 在web中的读

@PostMapping("/upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
    //read方法参数说明:
    //参数1:文件输入流
    //参数2:与表格数据对应的对象类型
    //参数3:监听器
    EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO))
            .sheet()
            .doRead();
    return "success";
}

相关文章:

  • 交通状态预测 | Python实现基于LSTM的客流量预测方法
  • 一条sql语句在MySQL的执行流程
  • 当遇到听不了的歌,Python程序员都是这么做的...
  • leetcode-289:生命游戏
  • C语言中的结构体应用详解及注意事项
  • 【2022】Elasticsearch-7.17.6集群部署
  • 计算器——位运算(c语言)
  • Maven 基础 5 第一个Maven 项目(IDEA 生成)
  • TypeScript算法题实战——哈希表篇
  • 嵌入式分享合集71
  • 又一巅峰神作 14年工作经验大佬手写“微服务项目下高并发的流量治理”,太牛了
  • 谷歌翻译失败解决方案
  • 网络安全面试题目及详解
  • 【初阶与进阶C++详解】第二十三篇:异常(异常抛出+异常捕获+异常优缺点)
  • 第二弹:看了图像分割一系列Unet、DeepLabv3+改进期刊论文,总结改进创新的一些相同点
  • 【391天】每日项目总结系列128(2018.03.03)
  • Apache Pulsar 2.1 重磅发布
  • django开发-定时任务的使用
  • Elasticsearch 参考指南(升级前重新索引)
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • linux安装openssl、swoole等扩展的具体步骤
  • NSTimer学习笔记
  • python学习笔记 - ThreadLocal
  • Redis中的lru算法实现
  • Redux 中间件分析
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • 编写符合Python风格的对象
  • 第2章 网络文档
  • 构建二叉树进行数值数组的去重及优化
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 前端工程化(Gulp、Webpack)-webpack
  • 网络应用优化——时延与带宽
  • 延迟脚本的方式
  • 一个SAP顾问在美国的这些年
  • 鱼骨图 - 如何绘制?
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • kubernetes资源对象--ingress
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (简单) HDU 2612 Find a way,BFS。
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)Sql Server 保留几位小数的两种做法
  • ***检测工具之RKHunter AIDE
  • .NET 的程序集加载上下文
  • ::什么意思
  • @RequestParam详解
  • [Android]使用Git将项目提交到GitHub
  • [Android开源]EasySharedPreferences:优雅的进行SharedPreferences数据存储操作
  • [Angularjs]asp.net mvc+angularjs+web api单页应用
  • [AutoSar NVM] 存储架构
  • [codevs 1296] 营业额统计