阿里Easyexcel是什麼呢?我們先來看看官方描述:
Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個嚴重的問題就是非常的耗內存,poi有一套SAX模式的API可以一定程度的解決一些內存溢出的問題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓後存儲都是在內存中完成的,內存消耗依然很大。easyexcel重寫了poi對07版Excel的解析,能夠原本一個3M的excel用POI sax依然需要100M左右內存降低到幾M,並且再大的excel不會出現內存溢出,03版依賴POI的sax模式。在上層做了模型轉換的封裝,讓使用者更加簡單方便。
概括要點就是:
第一點:Easyexcel重寫了poi對07版Excel的解析,內存使用率大大降低。
第二點:Easyexcel封裝了poi對03版Excel的解析,在使用上,變得更加簡潔。
版本
Easyexcel的使用,選對版本很重要,不然會報錯。官方推薦的版本清單如下:
poi版本:3.17及以上
easyexcel版本:建議使用2.0.x及以上
本文的討論及代碼,基於以下版本:
poi版本:3.17
easyexcel版本:建議使用2.0.6及以上
pom文件部分截圖如下:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!--alibaba easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
開發步驟
第一步:實現AnalysisEventListener抽象類
AnalysisEventListener提供2種方式處理excel,同步和異步。
同步處理直接使用SyncReadListener,easyexcel已經幫我們實現了。
異步處理需要我們自己實現AnalysisEventListener抽象類。
public class EasyExcelCommonListener extends AnalysisEventListener
我們這裏只討論異步處理方式。異步處理excel文件的功能。文檔註釋如下:
Receives the return of each piece of data parsed
翻譯過來就是:接收被解析的每個數據塊的返回值。這裏的數據塊,其實就是行記錄,easyexcel是一行一行來解析的。
這裏我有一個疑問:爲了提升性能,這裏的塊,是否可以爲N條記錄的集合,比如一次讀取1000條記錄。
在寫異步監聽處理器的邏輯時,一般的思路是把處理結果封裝到一個list集合中返回,如下:
private List<List<String>> datas = new ArrayList<>();
構建ExcelReader的方式不同,逐行解析返回的數據結構是不同的。可能有兩種情況:
LinkedHashMap<String, String>
ArrayLIst<String>
數據結構爲ArrayLIst<String>的情況:
數據結構爲LinkedHashMap<String, String>的情況:
官方描述也很清楚,每處理一行,都會回調監聽器的invoke方法。
此時,我們可以把解析結果一行一行的塞進list集合。
完整的代碼,這裏不再貼出,網上有很多,找一個改改就行,關鍵是要理解思路。
第二步:使用EasyExcelFactory讀取文件
// 阿里Easyexcel正確使用姿勢
// 1,使用EasyExcelFactory.read構建ExcelReader,官方推薦的做法。
// 2,指定excel文件格式(03版/07版)
excelReader = EasyExcelFactory.read(new FileInputStream(filePath), listener).excelType(choiceExcelType(filePath)).build();
read方法源碼如下:
/**
* Build excel the read
*
* @param inputStream
* Input stream to read.
* @param readListener
* Read listener.
* @return Excel reader builder.
*/
public static ExcelReaderBuilder read(InputStream inputStream, ReadListener readListener) {
return read(inputStream, null, readListener);
}
除了傳入流外,EasyExcelFactory還支持傳入文件路徑或者File的方式來構建EasyExcel。
具體細節,可以參考EasyExcelFactory.class。
Warn:不要直接new ExcelReader,官方文件已經棄用了這種方式。
// 阿里Easyexcel錯誤使用姿勢
// 1,直接new ExcelReader(new FileInputStream(filePath), choiceExcelType(filePath), listener)
如下:ExcelReader構造方法已經被標註了@ Deprecated註解(easyexcel版本:2.0.6)
/**
* Create new reader
*
* @param in
* the POI filesystem that contains the Workbook stream
* @param customContent
* {@link AnalysisEventListener#invoke(Object, AnalysisContext) }AnalysisContext
* @param eventListener
* Callback method after each row is parsed
* @deprecated please use {@link EasyExcelFactory#read()} build 'ExcelReader'
*/
@Deprecated
public ExcelReader(InputStream in, Object customContent, AnalysisEventListener eventListener) {
this(in, customContent, eventListener, true);
}
核心代碼方法整理如下:
/**
* 使用easyExcel讀取excel文件
* @param filePath
* @return 返參List<List<String>>,如果需要轉化成List<String>,可以自己來寫方法轉化
*/
public static List<List<String>> readExcelCommon(String filePath) {
// 異步監聽器,自己實現
EasyExcelListener listener = new EasyExcelListener();
// 讀取器
ExcelReader excelReader = null;
try{
// excelType()方法可以指定處理的exel的文件格式。如果不指定,系統會進行識別。
excelReader = EasyExcelFactory.read(new FileInputStream(filePath), listener).excelType(ExcelTypeEnum.XLS).build();
} catch (FileNotFoundException e) {
log.error("FileNotFoundException ", e);
}
// 開始讀取文件
excelReader.readAll();
// 獲取文件內容
List<List<String>> datas = listener.getDatas();
// 關閉讀取器,釋放資源
excelReader.finish();
return datas;
}
EasyExcelListener代碼如下:
/**
* 自定義讀取文件監聽器
*/
@Slf4j
public class EasyExcelListener extends AnalysisEventListener {
/**
* 存放文件內容的集合
*/
private List<List<String>> datas = new ArrayList<>();
/**
* 每解析一行都會回調invoke()方法
*
* @param object 讀取後的數據對象
* @param context 內容
*/
@Override
public void invoke(Object object, AnalysisContext context) {
Map<String, String> map = (Map<String, String>) object;
datas.add(new ArrayList<>(map.values()));
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
//注意不要調用datas.clear(),否則getDatas爲null
}
/**
* 返回數據
*
* @return 返回讀取的數據集合
**/
public List<List<String>> getDatas() {
return datas;
}
/**
* 設置讀取的數據集合
*
* @param datas 設置讀取的數據集合
**/
public void setDatas(List<List<String>> datas) {
this.datas = datas;
}
}
OK,開發下來,確實簡化了poi的開發步驟。