【Java】EasyExcel使用方法

一、 EasyExcel概述

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

二、 EasyExcel核心类简述

2.1 EasyExcel

EasyExcel继承自EasyExcelFactory工厂类,是EasyExcel的入口类,用于构建读和写的方法,通过不同的参数可以实现不同方式读写文件。EasyFactory用于实现具体的方法。

调用EasyExcel类的静态方法read(),通过传入不同的参数实现不同方式读取数据,较为常用的是read(String pathName, Class head, ReadListener readListener)方法,参数pathName为要读取文件的文件路径,参数head为参照该实体类读取数据,参数readListener为在读取数据时所执行的监听器。返回值为ExcelReaderBuilder类。

其他读取Excel的方法:

方法参数返回值
read()ExcelReaderBuilder
read(File file)file 待读取文件ExcelReaderBuilder
read(File file, ReadListener readListener)file 待读取文件
readListener 监听器
ExcelReaderBuilder
read(File file, Class head, ReadListener readListener)file 读取文件
head 按照实体类读取数据
readListener 监听器
ExcelReaderBuilder
read(String pathName)pathName 待读取文件路径ExcelReaderBuilder
read(String pathName, ReadListener readListener)pathName 待读取文件路径
readListener 监听器
ExcelReaderBuilder
read(String pathName, Class head, ReadListener readListener)pathName 待读取文件路径
head 按照实体类读取数据
readListener 监听器
ExcelReaderBuilder
read(InputStream inputStream)inputStream 待读取文件文件流ExcelReaderBuilder
read(InputStream inputStream, ReadListener readListener)inputStream 待读取文件文件流
readListener 监听器
ExcelReaderBuilder
read(InputStream inputStream, Class head, ReadListener readListener)inputStream 待读取文件文件流
head 按照实体类读取数据
readListener 监听器
ExcelReaderBuilder

调用EasyExcel类的静态方法write(),通过传入不同的参数实现不同方式读取数据,较为常用的是write(String pathName, Class head)方法,参数pathName为要读取文件的文件路径,参数head为参考该实体类导出数据。返回值为ExcelWriterBuilder类。

其他写Excel方法:

方法参数返回值
write()ExcelWriterBuilder
write(File file)file 待输出文件ExcelWriterBuilder
write(File file, Class head)file 待输出文件
head 按照实体类输出数据
ExcelWriterBuilder
write(String pathName)pathName 待输出文件路径ExcelWriterBuilder
write(String pathName, Class head)pathName 待输出文件路径
head 按照实体类输出数据
ExcelWriterBuilder
write(OutputStream outputStream)inputStream 待输出文件文件流ExcelWriterBuilder
write(OutputStream outputStream, Class head)inputStream 待输出文件文件流
head 按照实体类输出数据
ExcelWriterBuilder

ExcelReaderBuilder提供读取Excel文件数据的读取方式以及一些工作簿属性的设置。

2.2 ExcelReaderBuilderExcelWriterBuilder

ExcelReaderBuilderExcelWriterBuilder用于创建ReadWorkbookWriteWorkbook,可以理解为用于构建Excel的文件。

2.2.1 ExcelReaderBuilder

  • 设置ReadWorkbook属性
方法参数返回值描述
ExcelReaderBuilder()//ExcelReaderBuilder类的构造方法,同时创建ReadWorkbook的实体类
excelType(ExcelTypeEnum excelType)excelType Excel类型(枚举类型)ExcelReaderBuilder设置Excel的文件类型,明确读取的是xls、xlsx还是cvs文件
autoCloseStream(Boolean autoCloseStream)autoCloseStream 是否自动关闭流(布尔类型)ExcelReaderBuilder设置读取完毕后是否自动关闭流,默认为自动关闭
ignoreEmptyRow(Boolean ignoreEmptyRow)ignoreEmptyRow 是否忽略空行(布尔类型)ExcelReaderBuilder设置是否忽略空行,默认自动忽略
extraRead(CellExtraTypeEnum extraType)extraType 单元格属性类型(枚举类型)ExcelReaderBuilder读取单元格的批注、超链接和合并单元格属性

  • 创建ExcelReader

ExcelReader作为EasyExcel的核心实现类,用于实现文件的读取,调用build()方法会返回一个ExcelReader对象,并传入设置好的readWorkbook属性。

public ExcelReader build() {
	return new ExcelReader(readWorkbook);
}

  • 创建ExcelReaderSheetBuilder

如果是指定读取某个工作簿的内容,可以先调用sheet()方法设置当前需要读取的是第几个工作簿或指定工作簿的名称。不指定参数则获取所有工作簿的内容。在调用ExcelReaderSheetBuilder类的doRead()doReadSync()方法读取数据。

方法参数返回值
sheet()/ExcelReaderSheetBuilder
sheet(Integer sheetNo)sheetNo 工作簿序号(从0开始)ExcelReaderSheetBuilder
sheet(String sheetName)sheetName 工作簿名称ExcelReaderSheetBuilder
sheet(Integer sheetNo, String sheetName)sheetNo 工作簿序号(从0开始)
sheetName 工作簿名称
ExcelReaderSheetBuilder

  • 读取数据

若需要读取全部工作簿的数据,可以直接调用doReadAll()方法。

public void doReadAll() {
    ExcelReader excelReader = build();
    excelReader.readAll();
    excelReader.finish();
}

如果需要将结果拿出来再处理,可以调用doReadAllSync()方法,可以返回List<T>对应的泛型集合。

public <T> List<T> doReadAllSync() {
    SyncReadListener syncReadListener = new SyncReadListener();
    registerReadListener(syncReadListener);
    ExcelReader excelReader = build();
    excelReader.readAll();
    excelReader.finish();
    return (List<T>)syncReadListener.getList();
}

2.2.2 ExcelWriterBuilder

  • 设置WriteWorkbook属性
方法参数返回值描述
ExcelWriterBuilder()//ExcelWriterBuilder类的构造方法,同时创建WriteWorkbook的实体类
excelType(ExcelTypeEnum excelType)excelType Excel类型(枚举类型)ExcelWriterBuilder设置Excel的文件类型,明确读取的是xls、xlsx还是cvs文件
autoCloseStream(Boolean autoCloseStream)autoCloseStream 是否自动关闭流(布尔类型)ExcelWriterBuilder设置读取完毕后是否自动关闭流,默认为自动关闭
inMemory(Boolean inMemory)inMemory 是否在内存中运行ExcelWriterBuilder设置是否将数据读取到内存中,默认为false

  • 创建ExcelWriter

ExcalWriter作为EasyExcel写文件的核心类,用于实现输出Excel,可以调用ExcelWriterBuilder类的build()方法创建ExcalWriter类,并将设置好的writeWorkbook。示例:

public ExcelWriter build() {
    return new ExcelWriter(writeWorkbook);
}

  • 创建ExcelWriterSheetBuilder

调用ExcelWriterBuilder类的sheet()方法,用于设置将该数据流写入文件的那个工作簿,并设置工作簿的名称

方法参数返回值
sheet()/ExcelWriterSheetBuilder
sheet(Integer sheetNo)sheetNo 工作簿序号(从0开始)ExcelWriterSheetBuilder
sheet(String sheetName)sheetName 工作簿名称ExcelWriterSheetBuilder
sheet(Integer sheetNo, String sheetName)sheetNo 工作簿序号(从0开始)
sheetName 工作簿名称
ExcelWriterSheetBuilder

如果不设置工作簿名称,则输出在第一个工作簿并且工作簿名称为0

  • 输出文件

调用ExcelWriter类的write(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable)方法,参数data为需要输出的数据集合,参数writeSheet为需要写入的工作簿信息,参数writeTable为写入的table(若不同的几组数据写入同一个工作簿可以创建多个WriteTable,可以为空)。

public ExcelWriter write(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable) {
    excelBuilder.addContent(data, writeSheet, writeTable);
    return this;
}

2.3 ExcelReaderSheetBuilderExcelWriterSheetBuilder

ExcelReaderSheetBuilderExcelWriterSheetBuilder用于创建ExcelReaderExcelWriter类,可以理解为创建Excel的工作表。

2.3.1 ExcelReaderSheetBuilder

  • 设置ReadSheet属性
方法参数返回值描述
ExcelReaderSheetBuilder()\\构造函数,用于创建ReadSheet
ExcelReaderSheetBuilder(ExcelReader excelReader)ExcelReader excelReader\构造函数,用于创建ReadSheetExcelReader
sheetNo(Integer sheetNo)Integer sheetNo 工作表序号,从0开始ExcelReaderSheetBuilder用于设置读取哪个工作表
sheetName(String sheetName)String sheetName 工作表名称ExcelReaderSheetBuilder用于设置读取哪个工作表

  • 获取ReadSheet对象

调用build()方法,可以返回ReadSheet属性。

public ReadSheet build() {
    return readSheet;
}

  • 读取文件
/**
 * Sax read
 */
public void doRead() {
    if (excelReader == null) {
        throw new ExcelGenerateException("Must use 'EasyExcelFactory.read().sheet()' to call this method");
    }
    excelReader.read(build());
    excelReader.finish();
}

/**
 * Synchronous reads return results
 *
 * @return
 */
public <T> List<T> doReadSync() {
    if (excelReader == null) {
        throw new ExcelAnalysisException("Must use 'EasyExcelFactory.read().sheet()' to call this method");
    }
    SyncReadListener syncReadListener = new SyncReadListener();
    registerReadListener(syncReadListener);
    excelReader.read(build());
    excelReader.finish();
    return (List<T>)syncReadListener.getList();
}

2.3.2 ExcelWriterSheetBuilder

  • 设置WriteSheet属性
方法参数返回值描述
ExcelWriterSheetBuilder()\\构造函数,用于创建WriteSheet
ExcelWriterSheetBuilder(ExcelWriter excelWriter)ExcelWriter excelWriter\构造函数,用于创建WriteSheetExcelWriter
sheetNo(Integer sheetNo)Integer sheetNo 工作表序号ExcelWriterSheetBuilder设置工作表
sheetName(String sheetName)String sheetName 工作表名称ExcelWriterSheetBuilder设置工作表
  • 创建WriteSheet对象

调用build()方法,可以返回WriteSheet属性。

public WriteSheet build() {
    return writeSheet;
}
  • 创建文件

使用doWrite()方法可以向文件直接填充数据

public void doWrite(Collection<?> data) {
    if (excelWriter == null) {
        throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method");
    }
    excelWriter.write(data, build());
    excelWriter.finish();
}

public void doWrite(Supplier<Collection<?>> supplier) {
    doWrite(supplier.get());
}

使用doFill()方法可以根据模板向文件填充数据

public void doFill(Object data) {
    doFill(data, null);
}

public void doFill(Object data, FillConfig fillConfig) {
    if (excelWriter == null) {
        throw new ExcelGenerateException("Must use 'EasyExcelFactory.write().sheet()' to call this method");
    }
    excelWriter.fill(data, fillConfig, build());
    excelWriter.finish();
}

public void doFill(Supplier<Object> supplier) {
    doFill(supplier.get());
}

public void doFill(Supplier<Object> supplier, FillConfig fillConfig) {
    doFill(supplier.get(), fillConfig);
}

2.4 ExcelReaderExcelWriter

该类用于提供读和写Excel的实现方法

2.4.1 ExcelReader

方法参数描述
ExcelReader(ReadWorkbook readWorkbook)ReadWorkbook readWorkbook构造函数,用于创建ExcelAnalyser
readAll()\读取所有工作表的数据
read(ReadSheet... readSheet)ReadSheet... readSheet读取指定的工作表
read(List<ReadSheet> readSheetList)List<ReadSheet> readSheetList读取指定的工作表

2.4.2 ExcelWriter

方法参数描述
ExcelWriter(WriteWorkbook writeWorkbook)WriteWorkbook writeWorkbook构造函数,用于创建ExcelBuilder
write(Collection<?> data, WriteSheet writeSheet)Collection<?> data 数据集
WriteSheet writeSheet 写工作表类
导出Excel文件
write(Collection<?> data, WriteSheet writeSheet, WriteTable writeTable)Collection<?> data 数据集
WriteSheet writeSheet 写工作表类
WriteTable writeTable 若为多个数据集写入一个表可以创建WriteTable分区导出
导出Excel文件
fill(Object data, WriteSheet writeSheet)Object data 数据集
WriteSheet writeSheet
使用模板填充数据
fill(Object data, FillConfig fillConfig, WriteSheet writeSheet)Object data 数据集
FillConfig fillConfig 填充配置
WriteSheet writeSheet
使用模板填充数据

三、示例

3.1 读

3.1.1 简单读取数据

为避免一次性读取过量数据导致内存崩溃,可以自己实现ReadListener接口分批读取数据,并在读取过程中进行数据处理(存储数据库等)操作。

实现示例:

@Test
public void simpleRead() {
    // 写法1:JDK8+ ,不用额外写一个DemoDataListener
    // since: 3.0.0-beta1
    String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
    // 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener<DemoData>()).sheet().doRead();
}

如果是自己实现ReadListener类需要注意该实现类无法被Spring管理,同时也无法使用注解创建对象,所以可以定义一个私有属性通过构造函数将Spring管理的类传入

package com.alibaba.easyexcel.test.demo.read;

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

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 模板的读取类
 *
 * @author Jiaju Zhuang
 */
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 3000;
    /**
     * 缓存的数据
     */
    private List<DemoData> list = new ArrayList<>(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list = new ArrayList<>(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        demoDAO.save(list);
        LOGGER.info("存储数据库成功!");
    }
}

对于Excel读取出来对应的实体类,如果不加注解会按照顺序对应实体类的属性,即第一列数据对应第一个属性依次类推。如果需要进行数据表与属性的绑定则需在实体类的属性上添加注解@ExcelProperty。该注解有两个参数,@value为属性与数据表的列名绑定,@index将属性与数据表的列数绑定,对于其他不需要绑定数据的属性可以添加@ExcelIgnore注解避免将错误的至赋给实体类。

对于一些需要进行数据类型转换或格式化的属性可以使用@ExcelPropertyconverter指定转换的实体类,对于时间格式可以使用@DateTimeFormat注解对时间类型数据进行格式化,对于数值型可以使用@NumberFormat注解格式化数值型,例如@NumberFormat("#.##%")将值转为百分比。

也可以重写ExcelReaderBuilder继承自AbstractExcelReaderParameterBuilder类的registerReadListener方法,使用该方法后会对实体类虽有符合的属性进行转化,不如注解的形式灵活。

3.1.2 读取单元格的额外属性(超链接、批注、合并单元格信息)

除了对简单数据表读取功能外,EasyExcel还支持读取合并单元格、超链接、批注的信息读取。调用ExcelReaderBuilderextraRead()方法设置要获取的属性。通过调用DemoExtraListener类重写的extra()获取读取的额外属性。示例:

@Test
public void extraRead() {
    String fileName = TestFileUtil.getPath() + "demo" + File.separator + "extra.xlsx";
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener())
        // 需要读取批注 默认不读取
        .extraRead(CellExtraTypeEnum.COMMENT)
        // 需要读取超链接 默认不读取
        .extraRead(CellExtraTypeEnum.HYPERLINK)
        // 需要读取合并单元格信息 默认不读取
        .extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();
}

3.1.3 读取文件中的异常处理

在实际应用过程中,我们无法保证用户填写的文件百分百准确。所以在读取到无法解析的数据时需要做容错处理,对异常数据进行抛出。可以在监听器中调用onException()方法处理无法处理的数据。示例:

@Test
public void exceptionRead() {
    String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, ExceptionDemoData.class, new DemoExceptionListener()).sheet().doRead();
}
@Data
public class ExceptionDemoData {
    /**
     * 用日期去接字符串 肯定报错
     */
    private Date date;
}
package com.alibaba.easyexcel.test.demo.read;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.fastjson.JSON;

/**
 * 读取转换异常
 *
 * @author Jiaju Zhuang
 */
public class DemoExceptionListener extends AnalysisEventListener<ExceptionDemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoExceptionListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<ExceptionDemoData> list = new ArrayList<ExceptionDemoData>();

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            LOGGER.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }

    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }

    @Override
    public void invoke(ExceptionDemoData data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        if (list.size() >= BATCH_COUNT) {
            saveData();
            list.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        LOGGER.info("存储数据库成功!");
    }
}

3.1.4 返回读取数据

如果需要将该数据再提取出来二次处理的话,可以使用doReadSync()方法,可以返回实体类的集合数据。

3.2 写

3.2.1 原数据输出

@Test
public void simpleWrite() {
    // 写法1 JDK8+
    // since: 3.0.0-beta1
    String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    // 如果这里想使用03 则 传入excelType参数即可
    EasyExcel.write(fileName, DemoData.class)
        .sheet("模板")
        .doWrite(() -> {
            // 分页查询数据
            return data();
        });
}

如果需要输出为其他格式的文件,可以通过调用ExcelWriteBuilderexcelType(ExcelTypeEnum excelType)设置输出的文件类型。

3.2.2 指定字段输出

若有些数据不需要输出或者需要单独输出某几列的内容,可以调用ExcelWriterBuilder继承自AbstractExcelWriterParameterBuilderexcludeColumnFiledNames(Collection<String> excludeColumnFiledNames)方法去除指定的列,将需要去除的列写成Set集合作为参数传入。调用includeColumnFiledNames(Collection<String> includeColumnFiledNames)方法可以指定需要导出的列,同样可以写成Set集合。

示例:

@Test
public void excludeOrIncludeWrite() {
    String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
    // 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。

    // 根据用户传入字段 假设我们要忽略 date
    Set<String> excludeColumnFiledNames = new HashSet<>();
    excludeColumnFiledNames.add("date");
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")
        .doWrite(data());

    fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
    // 根据用户传入字段 假设我们只要导出 date
    Set<String> includeColumnFiledNames = new HashSet<>();
    includeColumnFiledNames.add("date");
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")
        .doWrite(data());
}

也可以直接使用注解规定需要输出的属性和位置。@ExcelProperty可以设置导出后列的标题(通过设置value属性)和导出在第几列中(通过设置Index属性,从0开始)。

示例:

package com.alibaba.easyexcel.test.demo.write;

import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;

import lombok.Data;

/**
 * 基础数据类
 *
 * @author Jiaju Zhuang
 **/
@Data
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;
}


3.2.3 格式化数据输出

当需要输出的字段存在格式要求时(时间、金额等)可以通过设置格式转换类对属性进行格式化,再输出为Excel时就能够按照既定格式输出。方法一:定义转换器,实现Convert接口对数据进行单独处理,在使用@ExcelPropertyconvert属性添加到实体类的属性上;方法二:对于简单的时间格式或数值格式,可以直接使用@DateFormat@NumberFormat注解(与读取格式化数据用法相同)。

示例:

package com.alibaba.easyexcel.test.demo.write;

import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;

import lombok.Data;

/**
 * 基础数据类.这里的排序和excel里面的排序一致
 *
 * @author Jiaju Zhuang
 **/
@Data
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;
}

package com.alibaba.easyexcel.test.demo.write;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

/**
 * String and string converter
 *
 * @author Jiaju Zhuang
 */
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里是读的时候会调用 不用管
     *
     * @param cellData            NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return
     */
    @Override
    public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return cellData.getStringValue();
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value               NotNull
     * @param contentProperty     Nullable
     * @param globalConfiguration NotNull
     * @return
     */
    @Override
    public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new WriteCellData<>("自定义:" + value);
    }

}

@Test
public void converterWrite() {
    String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());
}

3.2.4 设置单元格样式、超链接、备注、公式

先根据WriteCellData实现一个待输出的数据类类似于WriteCellDemoData,超链接、备注、公式、样式的类型都为WriteCellData类型。对于需要设置样式的属性,可以通过创建对象将属性赋给各自对象即可。

ClassProperty描述
HyperlinkDataString address 超链接的访问地址
HyperlinkType hyperlinkType 超链接的类型
超链接
CommentDataString author 备注作者
RichTextStringData richTextStringData 备注内容
备注
FormulaDataString formulaValue 表达式公式
WriteCellStyle见源码单元格样式
RichTextStringData见源码单元格多样式

实现:

package com.alibaba.easyexcel.test.demo.write;

import com.alibaba.excel.metadata.data.WriteCellData;

import lombok.Data;

/**
 * 根据WriteCellData写
 *
 * @author Jiaju Zhuang
 */
@Data
public class WriteCellDemoData {
    /**
     * 超链接
     *
     * @since 3.0.0-beta1
     */
    private WriteCellData<String> hyperlink;

    /**
     * 备注
     *
     * @since 3.0.0-beta1
     */
    private WriteCellData<String> commentData;

    /**
     * 公式
     *
     * @since 3.0.0-beta1
     */
    private WriteCellData<String> formulaData;

    /**
     * 指定单元格的样式。当然样式 也可以用注解等方式。
     *
     * @since 3.0.0-beta1
     */
    private WriteCellData<String> writeCellStyle;

    /**
     * 指定一个单元格有多个样式
     *
     * @since 3.0.0-beta1
     */
    private WriteCellData<String> richText;
}

@Test
public void writeCellDataWrite() {
    String fileName = TestFileUtil.getPath() + "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx";
    WriteCellDemoData writeCellDemoData = new WriteCellDemoData();

    // 设置超链接
    WriteCellData<String> hyperlink = new WriteCellData<>("官方网站");
    writeCellDemoData.setHyperlink(hyperlink);
    HyperlinkData hyperlinkData = new HyperlinkData();
    hyperlink.setHyperlinkData(hyperlinkData);
    hyperlinkData.setAddress("https://github.com/alibaba/easyexcel");
    hyperlinkData.setHyperlinkType(HyperlinkType.URL);

    // 设置备注
    WriteCellData<String> comment = new WriteCellData<>("备注的单元格信息");
    writeCellDemoData.setCommentData(comment);
    CommentData commentData = new CommentData();
    comment.setCommentData(commentData);
    commentData.setAuthor("Jiaju Zhuang");
    commentData.setRichTextStringData(new RichTextStringData("这是一个备注"));
    // 备注的默认大小是按照单元格的大小 这里想调整到4个单元格那么大 所以向后 向下 各额外占用了一个单元格
    commentData.setRelativeLastColumnIndex(1);
    commentData.setRelativeLastRowIndex(1);

    // 设置公式
    WriteCellData<String> formula = new WriteCellData<>();
    writeCellDemoData.setFormulaData(formula);
    FormulaData formulaData = new FormulaData();
    formula.setFormulaData(formulaData);
    // 将 123456789 中的第一个数字替换成 2
    // 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用
    formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");

    // 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。
    WriteCellData<String> writeCellStyle = new WriteCellData<>("单元格样式");
    writeCellStyle.setType(CellDataTypeEnum.STRING);
    writeCellDemoData.setWriteCellStyle(writeCellStyle);
    WriteCellStyle writeCellStyleData = new WriteCellStyle();
    writeCellStyle.setWriteCellStyle(writeCellStyleData);
    // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.
    writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
    // 背景绿色
    writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex());

    // 设置单个单元格多种样式
    WriteCellData<String> richTest = new WriteCellData<>();
    richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);
    writeCellDemoData.setRichText(richTest);
    RichTextStringData richTextStringData = new RichTextStringData();
    richTest.setRichTextStringDataValue(richTextStringData);
    richTextStringData.setTextString("红色绿色默认");
    // 前2个字红色
    WriteFont writeFont = new WriteFont();
    writeFont.setColor(IndexedColors.RED.getIndex());
    richTextStringData.applyFont(0, 2, writeFont);
    // 接下来2个字绿色
    writeFont = new WriteFont();
    writeFont.setColor(IndexedColors.GREEN.getIndex());
    richTextStringData.applyFont(2, 4, writeFont);

    List<WriteCellDemoData> data = new ArrayList<>();
    data.add(writeCellDemoData);
    EasyExcel.write(fileName, WriteCellDemoData.class).inMemory(true).sheet("模板").doWrite(data);
}

3.2.5 合并单元格

方法一:

使用注解@ContentLoopMerge用于循环扩展单元格作用于实体类的属性上,使用两个参数eachRow用于设置跨行合并单元格,columnExtend用于设置跨列合并单元格。注解@OnceAbsoluteMerge作用于实体类上,用于设置合并的单元格的范围,示例:

package com.alibaba.easyexcel.test.demo.write;

import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ContentLoopMerge;

import lombok.Data;

/**
 * 样式的数据类
 *
 * @author Jiaju Zhuang
 **/
@Data
// 将第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;
}


方法二:

单独设置合并单元格策略,调用ExcelWriterBuilderregisterWriteHandler方法,传入LoopMergeStrategyOnceAbsoluteMergeStrategy类自定义合并的策略(参数与使用注解的属性相同),示例:

@Test
public void mergeWrite() {
    // 方法1 注解
    String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
    // 在DemoStyleData里面加上ContentLoopMerge注解
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data());

    // 方法2 自定义合并单元格策略
    fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
    // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
    LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());
}

3.2.6 使用table输出

若需要向同一个工作簿中添加数据,可以设置不同的WriteTable

@Test
public void tableWrite() {
    String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";
    // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例
    // 这里 需要指定写用哪个class去写
    ExcelWriter excelWriter = null;
    try {
        excelWriter = EasyExcel.write(fileName, DemoData.class).build();
        // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
        // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
        WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();
        WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();
        // 第一次写入会创建头
        excelWriter.write(data(), writeSheet, writeTable0);
        // 第二次写如也会创建头,然后在第一次的后面写入数据
        excelWriter.write(data(), writeSheet, writeTable1);
    } finally {
        // 千万别忘记finish 会帮忙关闭流
        if (excelWriter != null) {
            excelWriter.finish();
        }
    }
}

3.3 填充

3.3.1 简单填充

使用对象填充,创建需要导出的Excel模板,在对应位置上填写导出数据对象的属性名称,并在doFill中传入实体类对象。或者是创建一个Map对象将属性和值作为键值对传入doFill方法中。

示例:

@Test
public void simpleFill() {
    // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
    String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx";

    // 方案1 根据对象填充
    String fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx";
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    FillData fillData = new FillData();
    fillData.setName("张三");
    fillData.setNumber(5.2);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);

    // 方案2 根据Map填充
    fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx";
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("name", "张三");
    map.put("number", 5.2);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}

在这里插入图片描述

3.3.2 填充列表

可以通过List集合输出数据,将所有数据填充进对应单元格中。

@Test
public void listFill() {
    // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
    // 填充list 的时候还要注意 模板中{.} 多了个点 表示list
    String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "list.xlsx";

    // 方案1 一下子全部放到内存里面 并填充
    String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data());

    // 方案2 分多次 填充 会使用文件缓存(省内存) jdk8
    // since: 3.0.0-beta1
    fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
    EasyExcel.write(fileName)
        .withTemplate(templateFileName)
        .sheet()
        .doFill(() -> {
            // 分页查询数据
            return data();
        });

    // 方案3 分多次 填充 会使用文件缓存(省内存)
    fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
    ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    excelWriter.fill(data(), writeSheet);
    excelWriter.fill(data(), writeSheet);
    // 千万别忘记关闭流
    excelWriter.finish();
}

在这里插入图片描述

3.3.3 复杂填充

填充功能可以事先在Excel中设计好格式和排版,再在后台将需要导出的数据传入fill方法中。

@Test
public void complexFill() {
    // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
    // {} 代表普通变量 {.} 代表是list的变量
    String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx";

    String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx";
    ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
    // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
    // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
    // 如果数据量大 list不是最后一行 参照下一个
    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
    excelWriter.fill(data(), fillConfig, writeSheet);
    excelWriter.fill(data(), fillConfig, writeSheet);
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("date", "2019年10月9日13:28:28");
    map.put("total", 1000);
    excelWriter.fill(map, writeSheet);
    excelWriter.finish();
}

在这里插入图片描述

注意中间注释内容,如果是使用List填充表格,会在最后添加一个空行,这里使用的是设置forceNewRow,但是会将所有数据加载到内存中。所以如果是大容量数据,可以利用ExcelWriterwrite()方法,利用3.2.6的方法将最后一行的内容追加在工作簿中。

示例:

@Test
public void complexFillWithTable() {
    // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
    // {} 代表普通变量 {.} 代表是list的变量
    // 这里模板 删除了list以后的数据,也就是统计的这一行
    String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complexFillWithTable.xlsx";

    String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx";
    ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    // 直接写入数据
    excelWriter.fill(data(), writeSheet);
    excelWriter.fill(data(), writeSheet);

    // 写入list之前的数据
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("date", "2019年10月9日13:28:28");
    excelWriter.fill(map, writeSheet);

    // list 后面还有个统计 想办法手动写入
    // 这里偷懒直接用list 也可以用对象
    List<List<String>> totalListList = new ArrayList<List<String>>();
    List<String> totalList = new ArrayList<String>();
    totalListList.add(totalList);
    totalList.add(null);
    totalList.add(null);
    totalList.add(null);
    // 第四列
    totalList.add("统计:1000");
    // 这里是write 别和fill 搞错了
    excelWriter.write(totalListList, writeSheet);
    excelWriter.finish();
    // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以
    // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案
}

在这里插入图片描述

这里最后的一行不再使用模板填充,直接写入sheet

除了纵向填充,还可以设置横向填充,示例:

@Test
public void horizontalFill() {
    // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
    // {} 代表普通变量 {.} 代表是list的变量
    String templateFileName =
        TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx";

    String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx";
    ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    // 设置文件填充方向
    FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
    excelWriter.fill(data(), fillConfig, writeSheet);
    excelWriter.fill(data(), fillConfig, writeSheet);

    Map<String, Object> map = new HashMap<String, Object>();
    map.put("date", "2019年10月9日13:28:28");
    excelWriter.fill(map, writeSheet);

    // 别忘记关闭流
    excelWriter.finish();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值