springboot整合EasyExcel
easyexcel是阿里开源的解析excel的工具,可以把它看作对poi的优化版。
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。
功能描述参考:https://blog.csdn.net/jiangjiandecsd/article/details/81115622
1、引入依赖
com.alibaba
easyexcel
1.1.2-beta5
依赖包是目前最新的版本
2、实体类
package com.xxx.xxxutils.utils;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
import lombok.*;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TableHeaderExcelProperty extends BaseRowModel{
/**
* value: 表头名称
* index: 列的号, 0表示第一列
*/
@ExcelProperty(value = "姓名", index = 0)
private String name;
@ExcelProperty(value = "年龄",index = 1)
private int age;
@ExcelProperty(value = "学校",index = 2)
private String school;
}
3、excel工具方法
package com.xxx.xxxutils.utils;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.util.StringUtils;
import com.xxx.xxxutils.listener.ExcelListener;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.poifs.filesystem.FileMagic;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Slf4j
public class ExcelUtil{
/**
* 从Excel中读取文件,读取的文件是一个DTO类,该类必须继承BaseRowModel
* 导入方法 第一个参数文件输入流,第二个参数实体类,必须继承自BaseRowModel
* 参考:https://github.com/alibaba/easyexcel
* 字符流必须支持标记,FileInputStream 不支持标记,可以使用BufferedInputStream 代替
* BufferedInputStream bis = new BufferedInputStream(new FileInputStream(...));
*/
public static List readExcel(final InputStream inputStream, final Class extends BaseRowModel> clazz) {
if (null == inputStream) {
throw new NullPointerException("the inputStream is null!");
}
ExcelListener listener = new ExcelListener<>();
// ExcelListener,读取大于1000行的数据才需要传入此参数
ExcelReader reader = new ExcelReader(inputStream, valueOf(inputStream), null, listener);
reader.read(new Sheet(1, 1, clazz));
return listener.getRows();
}
/**
* 从Excel中读取文件,读取的文件是一个DTO类,该类必须继承BaseRowModel
* 导出方法 第一个参数文件,第二个参数实体类集合,必须继承自BaseRowModel
*/
public static void writeExcel(final File file, List extends BaseRowModel> list) {
try (OutputStream out = new FileOutputStream(file)) {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
//写第一个sheet, 有模型映射关系
Class extends BaseRowModel> t = list.get(0).getClass();
Sheet sheet = new Sheet(1, 0, t);
writer.write(list, sheet);
writer.finish();
} catch (IOException e) {
log.warn("fail to write to excel file: file[{}]", file.getName(), e);
}
}
/**
* 根据输入流,判断为xls还是xlsx,该方法原本存在于easyexcel 1.1.0 的ExcelTypeEnum中。
*/
public static ExcelTypeEnum valueOf(InputStream inputStream) {
try {
FileMagic fileMagic = FileMagic.valueOf(inputStream);
if (FileMagic.OLE2.equals(fileMagic)) {
return ExcelTypeEnum.XLS;
}
if (FileMagic.OOXML.equals(fileMagic)) {
return ExcelTypeEnum.XLSX;
}
throw new IllegalArgumentException("excelTypeEnum can not null");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/****************************************************************************/
private static Sheet initSheet;
static {
initSheet = new Sheet(1, 0);
initSheet.setSheetName("sheet");
//设置自适应宽度
initSheet.setAutoWidth(Boolean.TRUE);
}
/**
* 导入方法,读取少于1000行数据
* @param filePath 文件绝对路径
* @return
*/
public static List
4、测试案例
package com.xxx.xxxutils;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.Table;
import com.xxx.xxxutils.domain.XxxInfo;
import com.xxx.xxxutils.model.MultiLineHeadExcelModelTest1;
import com.xxx.xxxutils.model.TableHeaderExcelProperty;
import com.xxx.xxxutils.model.User;
import com.xxx.xxxutils.utils.ExcelUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.xxx.xxxutils.utils.ExcelToolUtils.createTestListObject;
import static com.xxx.xxxutils.utils.ExcelToolUtils.createTestListStringHead2;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UtaxExcelSaleTest {
/**
* 07版本excel读数据量少于1千行数据自动转成javamodel,内部采用回调方法.
*
* @throws IOException 简单抛出异常,真实环境需要catch异常,同时在finally中关闭流
*/
@Test
//导入
public void simpleReadJavaModelV2007() throws IOException {
/*InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("d://台账信息" +
".xlsx");*/
//
//ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//InputStream inputStream = resolver.getResource("D:\\台账信息.xlsx").getInputStream();
InputStream inputStream = new FileInputStream("D:\\台账信息.xlsx");
List data = EasyExcelFactory.read(inputStream, new Sheet(1, 2, xxxInfo.class));
inputStream.close();
Xxx xxx= new Xxx();
for (int i = 0;i 无模型映射关系
//MultiLineHeadExcelModelTest1是写入excel的数据模型对象
Sheet sheet1 = new Sheet(1,3, MultiLineHeadExcelModelTest1.class,"第一个sheet",null);
//sheet1.setSheetName("testOne");
//设置列宽 设置每列的宽度
/*Map columnWidth = new HashMap();
columnWidth.put(0,10000);
columnWidth.put(1,40000);columnWidth.put(2,10000);columnWidth.put(3,10000);
sheet1.setColumnWidthMap(columnWidth);
sheet1.setHead(createTestListStringHead2());*/
//or 设置自适应宽度
//sheet1.setAutoWidth(Boolean.TRUE);
//参数一:写入的数据(要写入的结果集,list集合),参数二:写入的目标sheet
writer.write1(createTestListObject(), sheet1);
//将上下文中的最终OutputStream写入到指定文件中
writer.finish();
//关闭流
out.close();
Long endTime = System.currentTimeMillis();
Long time = endTime - startTime;
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println("excel导出共花费 "+time/1000+"秒");
}
@Test
//动态生成表头(导出)
public void writeV2() throws IOException {
Long startTime = System.currentTimeMillis();
//文件输出位置
OutputStream out = new FileOutputStream("outTest2.xlsx");
ExcelWriter writer = EasyExcelFactory.getWriter(out);
//写第一个sheet, sheet1 数据全是List 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
//创建一个表格,用于sheet中使用
Table table1 = new Table(1);
//设置列宽 设置每列的宽度
/*Map columnWidth = new HashMap();
columnWidth.put(0,10000);
columnWidth.put(1,40000);
columnWidth.put(2,10000);
columnWidth.put(3,10000);
sheet1.setColumnWidthMap(columnWidth);*/
table1.setHead(createTestListStringHead2());
//or 设置自适应宽度
//sheet1.setAutoWidth(Boolean.TRUE);
//参数一:写入的数据(要写入的结果集,list集合),参数二:写入的目标sheet
writer.write1(createTestListObject(), sheet1,table1);
//合并单元格
writer.merge(5,6,0,4);
//将上下文中的最终OutputStream写入到指定文件中
writer.finish();
//关闭流
out.close();
Long endTime = System.currentTimeMillis();
Long time = endTime - startTime;
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println("excel导出共花费 "+time/1000+"秒");
}
@Test
//导入
public void testRead(){
try (FileInputStream inputStream = new FileInputStream(ResourceUtils.getFile("classpath:excels/2003.xls"))) {
List users = ExcelUtil.readExcel(new BufferedInputStream(inputStream), User.class);
System.out.println(users);
} catch (IOException e) {
e.printStackTrace();
}
}
//========================================================================
/**
* 读取少于1000行的excle(导入)
*/
@Test
public void readLessThan1000Row(){
String filePath = "outTest2.xlsx";
List objects = ExcelUtil.readLessThan1000Row(filePath);
objects.forEach(System.out::println);
}
/**
* 读取少于1000行的excle,可以指定sheet和从几行读起(导入)
*/
@Test
public void readLessThan1000RowBySheet(){
String filePath = "outTest2.xlsx";
Sheet sheet = new Sheet(1, 1);
List objects = ExcelUtil.readLessThan1000RowBySheet(filePath,sheet);
objects.forEach(System.out::println);
}
/**
* 读取大于1000行的excle
* 带sheet参数的方法可参照测试方法readLessThan1000RowBySheet(),导入
*/
@Test
public void readMoreThan1000Row(){
String filePath = "outTest2.xlsx";
List objects = ExcelUtil.readMoreThan1000Row(filePath);
objects.forEach(System.out::println);
}
/**
* 生成excle
* 带sheet参数的方法可参照测试方法readLessThan1000RowBySheet(),导出
*/
@Test
public void writeBySimple(){
String filePath = "outTest1.xlsx";
List> data = new ArrayList<>();
data.add(Arrays.asList("111","222","333"));
data.add(Arrays.asList("111","222","333"));
data.add(Arrays.asList("111","222","333"));
List head = Arrays.asList("表头1", "表头2", "表头3");
ExcelUtil.writeBySimple(filePath,data,head);
}
/**
* 生成excle, 带用模型
* 带sheet参数的方法可参照测试方法readLessThan1000RowBySheet(),导出
*/
@Test
public void writeWithTemplate(){
String filePath = "outTest4.xlsx";
ArrayList data = new ArrayList<>();
for(int i = 0; i < 4; i++){
/*TableHeaderExcelProperty tableHeaderExcelProperty = new TableHeaderExcelProperty();
tableHeaderExcelProperty.setName("cmj" + i);
tableHeaderExcelProperty.setAge(22 + i);
tableHeaderExcelProperty.setSchool("清华大学" + i);*/
TableHeaderExcelProperty tableHeaderExcelProperty = TableHeaderExcelProperty
.builder()
.name("cmj" + i).age(22 + i).school("清华大学" + i)
.build();
data.add(tableHeaderExcelProperty);
}
ExcelUtil.writeWithTemplate(filePath,data);
}
/**
* 生成excle, 带用模型,带多个sheet,导出
*/
@Test
public void writeWithMultipleSheel(){
ArrayList list1 = new ArrayList<>();
for(int j = 1; j < 4; j++){
ArrayList list = new ArrayList<>();
for(int i = 0; i < 4; i++){
/*TableHeaderExcelProperty tableHeaderExcelProperty = new TableHeaderExcelProperty();
tableHeaderExcelProperty.setName("cmj" + i);
tableHeaderExcelProperty.setAge(22 + i);
tableHeaderExcelProperty.setSchool("清华大学" + i);*/
TableHeaderExcelProperty tableHeaderExcelProperty = TableHeaderExcelProperty.
builder()
.name("cmj" + i).age(22 + i).school("清华大学" + i)
.build();
list.add(tableHeaderExcelProperty);
}
Sheet sheet = new Sheet(j, 0);
sheet.setSheetName("sheet" + j);
ExcelUtil.MultipleSheelPropety multipleSheelPropety = new ExcelUtil.MultipleSheelPropety();
multipleSheelPropety.setData(list);
multipleSheelPropety.setSheet(sheet);
list1.add(multipleSheelPropety);
}
ExcelUtil.writeWithMultipleSheel("outTest5.xlsx",list1);
}
@Test
//遍历文件夹下文件
public void testFiles() throws FileNotFoundException {
//File file = ResourceUtils.getFile("classpath:templates/b_dfd.txt");
File file = ResourceUtils.getFile("classpath:excels");
if(file.exists()){
File[] files = file.listFiles();
if(files != null){
for(File childFile:files){
System.out.println(childFile.getName());
}
}
}
}
}
5、表头的添加
动态添加表头方法
package com.xxx.xxxutils.utils;
import com.alibaba.excel.metadata.Font;
import com.alibaba.excel.metadata.TableStyle;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
*@ClassName: ExcelToolUtils
*@Description: 无注解模式,动态添加表头,也可自由组合复杂表头
**/
public class ExcelToolUtils {
/**
*@Description: 无注解模式,动态添加表头1,也可自由组合复杂表头
**/
public static List> createTestListStringHead(){
//写sheet3 模型上没有注解,表头数据动态传入
List> head = new ArrayList>();
List headCoulumn1 = new ArrayList();
List headCoulumn2 = new ArrayList();
List headCoulumn3 = new ArrayList();
List headCoulumn4 = new ArrayList();
List headCoulumn5 = new ArrayList();
headCoulumn1.add("第一列");headCoulumn1.add("第一列");headCoulumn1.add("第一列");
headCoulumn2.add("第一列");headCoulumn2.add("第一列");headCoulumn2.add("第一列");
headCoulumn3.add("第二列");headCoulumn3.add("第二列");headCoulumn3.add("第二列");
headCoulumn4.add("第三列");headCoulumn4.add("第三列2");headCoulumn4.add("第三列2");
headCoulumn5.add("第一列");headCoulumn5.add("第3列");headCoulumn5.add("第4列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
head.add(headCoulumn4);
head.add(headCoulumn5);
return head;
}
/**
*@Description: 无注解模式,动态添加表头2,也可自由组合复杂表头
**/
public static List> createTestListStringHead2(){
List> head = new ArrayList>();
List headCoulumn1 = new ArrayList();
List headCoulumn2 = new ArrayList();
List headCoulumn3 = new ArrayList();
List headCoulumn4 = new ArrayList();
List headCoulumn5 = new ArrayList();
List headCoulumn6 = new ArrayList();
List headCoulumn7 = new ArrayList();
//如果行列里面都是一样的数据就直接合并为一个单元格
//第一列的第一行
headCoulumn1.add("第一列");
//第一列的第二行
headCoulumn1.add("第一列");
//第一列的第三行
headCoulumn1.add("第一列");
//第一列的第四行
headCoulumn1.add("22");
//第二列的第一行
headCoulumn2.add("第一列");
//第二列的第二行
headCoulumn2.add("第一列");
//第二列的第三行
headCoulumn2.add("第一列");
//第二列的第四行
headCoulumn2.add("33");
headCoulumn3.add("第二列");
headCoulumn3.add("第二列");
headCoulumn3.add("第二列");
headCoulumn3.add("第二列");
headCoulumn4.add("第三列");
headCoulumn4.add("第三列2");
headCoulumn4.add("第三列2");
headCoulumn4.add("第三列3");
headCoulumn5.add("第四列");
headCoulumn5.add("41");
headCoulumn5.add("42");
headCoulumn5.add("43");
headCoulumn6.add("第N列");
headCoulumn6.add("第3列");
headCoulumn6.add("第99列");
headCoulumn6.add("第100列");
headCoulumn7.add("第N列");
headCoulumn7.add("第3列");
headCoulumn7.add("第101列");
headCoulumn7.add("第102列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
head.add(headCoulumn4);
head.add(headCoulumn5);
head.add(headCoulumn6);
head.add(headCoulumn7);
return head;
}
/**
*@Description: 往表格添加数据
**/
public static List> createTestListObject() {
List> object = new ArrayList>();
for (int i = 0; i < 1000; i++) {
List da = new ArrayList();
da.add("字符串"+i);
da.add(Long.valueOf(187837834l+i));
da.add(Integer.valueOf(2233+i));
da.add(Double.valueOf(2233.00+i));
da.add(Float.valueOf(2233.0f+i));
da.add(new Date());
da.add(new BigDecimal("3434343433554545"+i));
da.add(Short.valueOf((short)i));
object.add(da);
}
return object;
}
/**
*@Description: 设置表格样式
**/
public static TableStyle createTableStyle(){
TableStyle tableStyle = new TableStyle();
//设置表头样式
Font headFont = new Font();
//字体是否加粗
headFont.setBold(true);
//字体大小
headFont.setFontHeightInPoints((short)12);
//字体
headFont.setFontName("楷体");
tableStyle.setTableHeadFont(headFont);
//背景色
tableStyle.setTableContentBackGroundColor(IndexedColors.BLUE);
//设置表格主题样式
Font contentFont = new Font();
contentFont.setBold(true);
contentFont.setFontHeightInPoints((short)12);
contentFont.setFontName("黑体");
tableStyle.setTableContentFont(contentFont);
tableStyle.setTableContentBackGroundColor(IndexedColors.GREEN);
return tableStyle;
}
}
通过注解方式添加表头
package com.xxx.xxxutils.model;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName: MultiLineHeadExcelModelTest1
* @Description: 复杂表头实体类
* ExayExcel 提供注解的方式, 来方便的定义 Excel 需要的数据模型
* ①:首先,定义的写入模型必须要继承自 BaseRowModel
* ②:通过 @ExcelProperty 注解来指定每个字段的列名称,以及下标位置;
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MultiLineHeadExcelModelTest1 extends BaseReadModel{
//如果行列里面都是一样的数据就直接合并为一个单元格
//---------------------第一列的第一行,第一列的第二行,第一列的第三行
@ExcelProperty(value = {"表头1","表头1","表头31"},index = 0)
private String p1;
//---------------------第二列的第一行,第二列的第二行,第二列的第三行
@ExcelProperty(value = {"表头1","表头1","表头32"},index = 1)
private String p2;
@ExcelProperty(value = {"表头3","表头3","表头3"},index = 2)
private int p3;
@ExcelProperty(value = {"表头4","表头4","表头4"},index = 3)
private long p4;
@ExcelProperty(value = {"表头5","表头51","表头52"},index = 4)
private String p5;
@ExcelProperty(value = {"表头6","表头61","表头611"},index = 5)
private String p6;
@ExcelProperty(value = {"表头6","表头61","表头612"},index = 6)
private String p7;
@ExcelProperty(value = {"表头6","表头62","表头621"},index = 7)
private String p8;
@ExcelProperty(value = {"表头6","表头62","表头622"},index = 8)
private String p9;
}
6、总结
easyexcel官方文档:https://github.com/alibaba/easyexcel/blob/master/README.md
easyexcel官方源码: https://github.com/alibaba/easyexcel
easyexcel博客参考:1、https://blog.csdn.net/qq_32258777/article/details/89031479
2、https://blog.csdn.net/jiangjiandecsd/article/details/81115622
3、https://mp.weixin.qq.com/s/BZyXnCzVoKU6a9jTxYf_Vg
动态表头的添加如果以日常操作excel的思维来理解还是很好弄懂的。