一、 介紹
Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個(gè)嚴(yán)重的問題就是非常的耗內(nèi)存,poi有一套SAX模式的API可以一定程度的解決一些內(nèi)存溢出的問題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓后存儲(chǔ)都是在內(nèi)存中完成的,內(nèi)存消耗依然很大。easyexcel重寫了poi對(duì)07版Excel的解析,一個(gè)3M的excel用POI sax解析依然需要100M左右內(nèi)存,改用easyexcel可以降低到幾M,并且再大的excel也不會(huì)出現(xiàn)內(nèi)存溢出;03版依賴POI的sax模式,在上層做了模型轉(zhuǎn)換的封裝,讓使用者更加簡(jiǎn)單方便
版本支持
2+版本支持Java7&Java6
3+版本支持Java8
詳細(xì)了解可以點(diǎn)擊官網(wǎng)文檔查閱
個(gè)人依賴:
<dependencies>
<!--easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<!--slf4j 日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
二、 讀Excel
Excel:

對(duì)象:
@Data
public class Question1 {
private String id; //題目ID
private String companyId; //所屬企業(yè)
private String catalogId; //題目所屬目錄ID
}
監(jiān)視器:
| 名稱 | 說明 |
|---|---|
| AnalysisEventListener<T> | 分析事件偵聽器:接收解析的每條數(shù)據(jù)的返回 |
| SyncReadListener | 同步讀取偵聽器 |
| AbstractIgnoreExceptionReadListener | 抽象忽略異常讀取偵聽器 |
| ModelBuildEventListener | 模型構(gòu)建事件偵聽器 |
AnalysisEventListener<T>
所有已實(shí)現(xiàn)的接口:Listener、ReadListener
直接已知子類:SyncReadListener
| 方法 | 返回值類型 | 說明 |
|---|---|---|
| boolean | hasNext | 驗(yàn)證是否有另一條數(shù)據(jù)。您可以通過返回 false 來(lái)停止讀取 |
| void | invokeHead | 分析第一行時(shí)觸發(fā)調(diào)用函數(shù) |
| void | invokeHeadMap | 以map的形式放回表頭,覆蓋當(dāng)前方法以接收表頭數(shù)據(jù) |
| void | onException | 當(dāng)任何一個(gè)監(jiān)聽器進(jìn)行錯(cuò)誤報(bào)告時(shí),所有監(jiān)聽器都會(huì)收到此方法 |
代碼:
private static void read2() {
final List list = new ArrayList();
//使用EasyExcel讀取test1.xlsx文件
EasyExcel.read("../test/test1.xlsx", Question1.class, new AnalysisEventListener<Question1>() {
//重寫子類方法
@Override
public void invoke(Question1 question1, AnalysisContext analysisContext) {
list.add(question1);
}
//重寫子類方法
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
@Override
public void invokeHeadMap(Map headMap, AnalysisContext context) {
System.out.println(headMap);
}
}
).doReadAll();
//獲取讀取到的數(shù)據(jù)
for (Object o : list) {
Question1 question1 = (Question1) o;
System.out.println(question1);
}
}
結(jié)果視圖:
SyncReadListener
所有已實(shí)現(xiàn)的接口:Listener、ReadListener
直接已知父類:AnalysisEventListener<T>
| 方法 | 返回值類型 | 說明 |
|---|---|---|
| void | doAfterAllAnalysed | 如果有什么操作在全部分析結(jié)束后執(zhí)行 |
| List<Object> | getList | |
| void | invoke | 當(dāng)分析一行觸發(fā)器調(diào)用函數(shù) |
| void | setList(List<Object> list) |
代碼:
private static void read1() {
final List list = new ArrayList();
//使用EasyExcel讀取test1.xlsx文件
EasyExcel.read("../test/test1.xlsx", Question1.class, new SyncReadListener() {
//EasyExcel在讀取excel表格時(shí),每讀取到一行,就會(huì)調(diào)用一次這個(gè)方法,
//并且將讀取到的行數(shù)據(jù),封裝到指定類型(Question1)的對(duì)象中,傳遞給我們(Object object)
/*
此問題可能出現(xiàn)在低版本的easyExcel中,出現(xiàn)時(shí)可以按照下列方式解決
如果表格數(shù)據(jù)不是頂行寫的,需要通過headRowNumber指定表頭行的數(shù)量
如果表格數(shù)據(jù)不是頂列寫的,需要在封裝的實(shí)體屬性上通過@ExcelProperty將實(shí)體屬性和表格列名進(jìn)行對(duì)應(yīng)
*/
@Override
public void invoke(Object object, AnalysisContext context) {
// System.out.println(object);
list.add(object);
}
}).doReadAll();
//獲取讀取到的數(shù)據(jù)
for (Object o : list) {
Question1 question1 = (Question1) o;
System.out.println(question1);
}
}
解決方式
//index屬性指定當(dāng)前這個(gè)對(duì)應(yīng)的是表格中哪個(gè)索引的列,表格中的索引是從0開始的
/**
* 強(qiáng)制讀取第三個(gè) 這里不建議 index 和 name 同時(shí)用,要么一個(gè)對(duì)象只用index,要么一個(gè)對(duì)象只用name去匹配
*/
@ExcelProperty(index = 2)
private String catalogId; //題目所屬目錄ID
/**
* 用名字去匹配,這里需要注意,如果名字重復(fù),會(huì)導(dǎo)致只有一個(gè)字段讀取到數(shù)據(jù)
*/
@ExcelProperty("題目id")
private String id; //題目ID
@ExcelProperty("企業(yè)id")
private String companyId; //所屬企業(yè)
三、 寫Excel (比較常用)
3.1、簡(jiǎn)單入門
private static void write() {
List list = new ArrayList();
list.add(new Question1("1", "1", "1"));
list.add(new Question1("2", "1", "1"));
list.add(new Question1("3", "1", "1"));
1.
EasyExcel
.write("test/test_w.xlsx", Question1.class)
.sheet()
.doWrite(list);
2.
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write("test/test_w.xlsx", Question1.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(list, writeSheet);
} finally {
// 千萬(wàn)別忘記finish 會(huì)幫忙關(guān)閉流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
3.2、 指定寫入的列
添加該實(shí)體列的注釋就可以實(shí)現(xiàn)如下:
@ExcelProperty(value = "題目id", index = 0)
private String id; //題目ID
@ExcelProperty(value = "企業(yè)id", index = 1)
private String companyId; //所屬企業(yè)
@ExcelProperty(value = "類目id", index = 3)
private String catalogId; //題目所屬目錄ID

3.2、 復(fù)雜表頭
添加該實(shí)體列的注釋就可以實(shí)現(xiàn)如下:
@ExcelProperty({"主標(biāo)題", "題目id"})
private String id; //題目ID
@ExcelProperty({"主標(biāo)題", "企業(yè)id"})
private String companyId; //所屬企業(yè)
@ExcelProperty({"主標(biāo)題", "類目id"})
private String catalogId; //題目所屬目錄ID

3.3、 列寬 行高
添加該實(shí)體列的注釋就可以實(shí)現(xiàn)如下:
@Data
@HeadRowHeight(30) //表頭行高
@ContentRowHeight(20) //數(shù)據(jù)行高
@ColumnWidth(25) //列寬
public class Question1 {
@ExcelProperty({"題目id"})
private String id; //題目ID
@ExcelProperty({"企業(yè)id"})
private String companyId; //所屬企業(yè)
//局部定義
@ColumnWidth(50)
@ExcelProperty({"類目id"})
private String catalogId; //題目所屬目錄ID
}
- 自定義列寬 行高
三、填充Excel
3.1、基于模板填充表格數(shù)據(jù)
- 寫出多條記錄
private static void write_template_multi() {
List list = new ArrayList();
list.add(new Question1("1", "1", "1"));
list.add(new Question1("2", "1", "1"));
list.add(new Question1("3", "1", "1"));
EasyExcel
.write("../test/test_w.xlsx", Question1.class)
.withTemplate("../test/test_template.xlsx")
.sheet()
.doFill(list);
}
- 多條數(shù)據(jù)
2.寫出一條記錄
private static void write_template_one() {
EasyExcel
.write("test/test_w.xlsx", Question1.class)
.withTemplate("test/test_template_one.xlsx")
.sheet()
.doFill(new Question1("1", "1", "1"));
}
- 一條數(shù)據(jù)
添加一條數(shù)據(jù)與添加多條數(shù)據(jù)的差別如下:
模板中參數(shù)中的前是否有.,若有則能多或單條條數(shù)據(jù)寫入;否則只允許單條數(shù)據(jù)寫入
- 一條模板
- 多條模板
3.1、復(fù)雜的填充
private static void write_template_multi_one() {
List list = new ArrayList();
list.add(new Question1("1", "1", "1"));
list.add(new Question1("2", "1", "1"));
list.add(new Question1("3", "1", "1"));
ExcelWriterBuilder writerBuilder = EasyExcel
.write("../test/test_w.xlsx", Question1.class)
.withTemplate("../test/test_template_mul_one.xlsx");
WriteSheet writeSheet = writerBuilder.sheet().build();
ExcelWriter excelWriter = writerBuilder.build();
//在填充玩多結(jié)果數(shù)據(jù)后,要強(qiáng)制換行,不然后續(xù)的單結(jié)果數(shù)據(jù)會(huì)發(fā)生覆蓋現(xiàn)象
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
excelWriter.fill(list, fillConfig, writeSheet);
Map map = new HashMap();
map.put("total", 600);
excelWriter.fill(map, writeSheet);
//調(diào)用finish方法
excelWriter.finish();
}
四、常見API
4.1、關(guān)于常見類解析
| 類名 | 說明 |
|---|---|
| EasyExcel | 入口類,用于構(gòu)建開始各種操作 |
| ExcelReaderBuilder ExcelWriterBuilder | 構(gòu)建出一個(gè) ReadWorkbook WriteWorkbook,可以理解成一個(gè)excel對(duì)象,一個(gè)excel只要構(gòu)建一個(gè) |
| ExcelReaderSheetBuilder ExcelWriterSheetBuilder | 構(gòu)建出一個(gè) ReadSheet WriteSheet對(duì)象,可以理解成excel里面的一頁(yè),每一頁(yè)都要構(gòu)建一個(gè) |
| ReadListener | 在每一行讀取完畢后都會(huì)調(diào)用ReadListener來(lái)處理數(shù)據(jù) |
| WriteHandler | 在每一個(gè)操作包括創(chuàng)建單元格、創(chuàng)建表格等都會(huì)調(diào)用WriteHandler來(lái)處理數(shù)據(jù) |
所有配置都是繼承的,Workbook的配置會(huì)被Sheet繼承,所以在用EasyExcel設(shè)置參數(shù)的時(shí)候,在EasyExcel...sheet()方法之前作用域是整個(gè)sheet,之后針對(duì)單個(gè)sheet
4.2、讀
注解
● ExcelProperty 指定當(dāng)前字段對(duì)應(yīng)excel中的那一列??梢愿鶕?jù)名字或者Index去匹配。當(dāng)然也可以不寫,默認(rèn)第一個(gè)字段就是index=0,以此類推。千萬(wàn)注意,要么全部不寫,要么全部用index,要么全部用名字去匹配。千萬(wàn)別三個(gè)混著用,除非你非常了解源代碼中三個(gè)混著用怎么去排序的。
● ExcelIgnore 默認(rèn)所有字段都會(huì)和excel去匹配,加了這個(gè)注解會(huì)忽略該字段
● DateTimeFormat 日期轉(zhuǎn)換,用String去接收excel日期格式的數(shù)據(jù)會(huì)調(diào)用這個(gè)注解。里面的value參照java.text.SimpleDateFormat
● NumberFormat 數(shù)字轉(zhuǎn)換,用String去接收excel數(shù)字格式的數(shù)據(jù)會(huì)調(diào)用這個(gè)注解。里面的value參照java.text.DecimalFormat
● ExcelIgnoreUnannotated 默認(rèn)不加ExcelProperty 的注解的都會(huì)參與讀寫,加了不會(huì)參與
4.2、寫
注解
● ExcelProperty index 指定寫到第幾列,默認(rèn)根據(jù)成員變量排序。value指定寫入的名稱,默認(rèn)成員變量的名字,多個(gè)value可以參照快速開始中的復(fù)雜頭
● ExcelIgnore 默認(rèn)所有字段都會(huì)寫入excel,這個(gè)注解會(huì)忽略這個(gè)字段
● DateTimeFormat 日期轉(zhuǎn)換,將Date寫到excel會(huì)調(diào)用這個(gè)注解。里面的value參照java.text.SimpleDateFormat
● NumberFormat 數(shù)字轉(zhuǎn)換,用Number寫excel會(huì)調(diào)用這個(gè)注解。里面的value參照java.text.DecimalFormat
● ExcelIgnoreUnannotated 默認(rèn)不加ExcelProperty 的注解的都會(huì)參與讀寫,加了不會(huì)參與






