- 一、 EasyExcel概述
- 二、 EasyExcel核心类简述
- 2.1 `EasyExcel`
- 2.2 `ExcelReaderBuilder`与`ExcelWriterBuilder`
- 2.2.1 `ExcelReaderBuilder`
- 2.2.2 `ExcelWriterBuilder`
- 2.3 `ExcelReaderSheetBuilder`与`ExcelWriterSheetBuilder`
- 2.3.1 `ExcelReaderSheetBuilder`
- 2.3.2 `ExcelWriterSheetBuilder`
- 2.4 `ExcelReader`与`ExcelWriter`
- 2.4.1 `ExcelReader`
- 2.4.2 `ExcelWriter`
- 三、示例
- 3.1 读
- 3.1.1 简单读取数据
- 3.1.2 读取单元格的额外属性(超链接、批注、合并单元格信息)
- 3.1.3 读取文件中的异常处理
- 3.1.4 返回读取数据
- 3.2 写
- 3.2.1 原数据输出
- 3.2.2 指定字段输出
- 3.2.3 格式化数据输出
- 3.2.4 设置单元格样式、超链接、备注、公式
- 3.2.5 合并单元格
- 3.2.6 使用table输出
- 3.3 填充
- 3.3.1 简单填充
- 3.3.2 填充列表
- 3.3.3 复杂填充
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 EasyExcelEasyExcel继承自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 ExcelReaderBuilder与ExcelWriterBuilderExcelReaderBuilder与ExcelWriterBuilder用于创建ReadWorkbook与WriteWorkbook,可以理解为用于构建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
public2.2.2 ExcelWriterBuilderList doReadAllSync() { SyncReadListener syncReadListener = new SyncReadListener(); registerReadListener(syncReadListener); ExcelReader excelReader = build(); excelReader.readAll(); excelReader.finish(); return (List )syncReadListener.getList(); }
- 设置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 ExcelReaderSheetBuilder与ExcelWriterSheetBuilder
ExcelReaderSheetBuilder与ExcelWriterSheetBuilder用于创建ExcelReader和ExcelWriter类,可以理解为创建Excel的工作表。
2.3.1 ExcelReaderSheetBuilder- 设置ReadSheet属性
| 方法 | 参数 | 返回值 | 描述 |
|---|---|---|---|
| ExcelReaderSheetBuilder() | 构造函数,用于创建ReadSheet | ||
| ExcelReaderSheetBuilder(ExcelReader excelReader) | ExcelReader excelReader | 构造函数,用于创建ReadSheet和ExcelReader | |
| sheetNo(Integer sheetNo) | Integer sheetNo 工作表序号,从0开始 | ExcelReaderSheetBuilder | 用于设置读取哪个工作表 |
| sheetName(String sheetName) | String sheetName 工作表名称 | ExcelReaderSheetBuilder | 用于设置读取哪个工作表 |
- 获取ReadSheet对象
调用build()方法,可以返回ReadSheet属性。
public ReadSheet build() {
return readSheet;
}
- 读取文件
public void doRead() {
if (excelReader == null) {
throw new ExcelGenerateException("Must use 'EasyExcelFactory.read().sheet()' to call this method");
}
excelReader.read(build());
excelReader.finish();
}
public List 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)syncReadListener.getList();
}
2.3.2 ExcelWriterSheetBuilder
- 设置WriteSheet属性
| 方法 | 参数 | 返回值 | 描述 |
|---|---|---|---|
| ExcelWriterSheetBuilder() | 构造函数,用于创建WriteSheet | ||
| ExcelWriterSheetBuilder(ExcelWriter excelWriter) | ExcelWriter excelWriter | 构造函数,用于创建WriteSheet和ExcelWriter | |
| 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> 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
2.4 ExcelReader与ExcelWriter
该类用于提供读和写Excel的实现方法
2.4.1 ExcelReader| 方法 | 参数 | 描述 |
|---|---|---|
| ExcelReader(ReadWorkbook readWorkbook) | ReadWorkbook readWorkbook | 构造函数,用于创建ExcelAnalyser类 |
| readAll() | 读取所有工作表的数据 | |
| read(ReadSheet... readSheet) | ReadSheet... readSheet | 读取指定的工作表 |
| read(List | List | 读取指定的工作表 |
| 方法 | 参数 | 描述 |
|---|---|---|
| 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 | 使用模板填充数据 |
为避免一次性读取过量数据导致内存崩溃,可以自己实现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()).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; // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 public class DemoDataListener extends AnalysisEventListener{ private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class); private static final int BATCH_COUNT = 3000; private List list = new ArrayList<>(BATCH_COUNT); private DemoDAO demoDAO; public DemoDataListener(DemoDAO demoDAO) { this.demoDAO = demoDAO; } @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); } } @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注解避免将错误的至赋给实体类。
对于一些需要进行数据类型转换或格式化的属性可以使用@ExcelProperty的converter指定转换的实体类,对于时间格式可以使用@DateTimeFormat注解对时间类型数据进行格式化,对于数值型可以使用@NumberFormat注解格式化数值型,例如@NumberFormat("#.##%")将值转为百分比。
3.1.2 读取单元格的额外属性(超链接、批注、合并单元格信息)也可以重写ExcelReaderBuilder继承自AbstractExcelReaderParameterBuilder类的registerReadListener方法,使用该方法后会对实体类虽有符合的属性进行转化,不如注解的形式灵活。
除了对简单数据表读取功能外,EasyExcel还支持读取合并单元格、超链接、批注的信息读取。调用ExcelReaderBuilder的extraRead()方法设置要获取的属性。通过调用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; public class DemoExceptionListener extends AnalysisEventListener3.1.4 返回读取数据{ private static final Logger LOGGER = LoggerFactory.getLogger(DemoExceptionListener.class); private static final int BATCH_COUNT = 5; List list = new ArrayList (); @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()); } } @Override public void invokeHeadMap(Map 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("存储数据库成功!"); } }
如果需要将该数据再提取出来二次处理的话,可以使用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();
});
}
3.2.2 指定字段输出如果需要输出为其他格式的文件,可以通过调用ExcelWriteBuilder的excelType(ExcelTypeEnum excelType)设置输出的文件类型。
若有些数据不需要输出或者需要单独输出某几列的内容,可以调用ExcelWriterBuilder继承自AbstractExcelWriterParameterBuilder的excludeColumnFiledNames(Collection
示例:
@Test
public void excludeOrIncludeWrite() {
String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
// 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。
// 根据用户传入字段 假设我们要忽略 date
Set 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 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;
@Data
public class IndexData {
@ExcelProperty(value = "字符串标题", index = 0)
private String string;
@ExcelProperty(value = "日期标题", index = 1)
private Date date;
@ExcelProperty(value = "数字标题", index = 3)
private Double doubleData;
}
3.2.3 格式化数据输出
当需要输出的字段存在格式要求时(时间、金额等)可以通过设置格式转换类对属性进行格式化,再输出为Excel时就能够按照既定格式输出。方法一:定义转换器,实现Convert接口对数据进行单独处理,在使用@ExcelProperty的convert属性添加到实体类的属性上;方法二:对于简单的时间格式或数值格式,可以直接使用@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;
@Data
public class ConverterData {
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
@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; public class CustomStringStringConverter implements Converter{ @Override public Class> supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public String convertToJavaData(ReadCellData> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return cellData.getStringValue(); } @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类型。对于需要设置样式的属性,可以通过创建对象将属性赋给各自对象即可。
| Class | Property | 描述 |
|---|---|---|
| HyperlinkData | String address 超链接的访问地址 HyperlinkType hyperlinkType 超链接的类型 | 超链接 |
| CommentData | String author 备注作者 RichTextStringData richTextStringData 备注内容 | 备注 |
| FormulaData | String formulaValue 表达式 | 公式 |
| WriteCellStyle | 见源码 | 单元格样式 |
| RichTextStringData | 见源码 | 单元格多样式 |
实现:
package com.alibaba.easyexcel.test.demo.write;
import com.alibaba.excel.metadata.data.WriteCellData;
import lombok.Data;
@Data
public class WriteCellDemoData {
private WriteCellData hyperlink;
private WriteCellData commentData;
private WriteCellData formulaData;
private WriteCellData writeCellStyle;
private WriteCellData richText;
}
@Test
public void writeCellDataWrite() {
String fileName = TestFileUtil.getPath() + "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx";
WriteCellDemoData writeCellDemoData = new WriteCellDemoData();
// 设置超链接
WriteCellData 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 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 formula = new WriteCellData<>();
writeCellDemoData.setFormulaData(formula);
FormulaData formulaData = new FormulaData();
formula.setFormulaData(formulaData);
// 将 123456789 中的第一个数字替换成 2
// 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用
formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");
// 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。
WriteCellData 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 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 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;
@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;
}
方法二:
单独设置合并单元格策略,调用ExcelWriterBuilder的registerWriteHandler方法,传入LoopMergeStrategy和OnceAbsoluteMergeStrategy类自定义合并的策略(参数与使用注解的属性相同),示例:
@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 map = new HashMap();
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 map = new HashMap();
map.put("date", "2019年10月9日13:28:28");
map.put("total", 1000);
excelWriter.fill(map, writeSheet);
excelWriter.finish();
}
注意中间注释内容,如果是使用List填充表格,会在最后添加一个空行,这里使用的是设置forceNewRow,但是会将所有数据加载到内存中。所以如果是大容量数据,可以利用ExcelWriter的write()方法,利用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 map = new HashMap();
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
// list 后面还有个统计 想办法手动写入
// 这里偷懒直接用list 也可以用对象
List> totalListList = new ArrayList>();
List totalList = new ArrayList();
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 map = new HashMap();
map.put("date", "2019年10月9日13:28:28");
excelWriter.fill(map, writeSheet);
// 别忘记关闭流
excelWriter.finish();
}



