EasyExcel的使用背景
工作中總會(huì)遇到對(duì)Excel讀寫功能,之前接觸過(guò)EasyExcel,后續(xù)我們基本上用它代替了傳統(tǒng)的POI和JXL、甚至還有一個(gè)EasyPOI技術(shù)。
EasyExcel的時(shí)候痛點(diǎn)
使用的EasyExcel時(shí)候,一般場(chǎng)景下表頭比較傳統(tǒng),也不復(fù)雜,但是這次呢表頭稍微有點(diǎn)復(fù)雜,讀取數(shù)據(jù)要從指定的位置開(kāi)始,要從指定位置開(kāi)始讀取EasyExcel,所以呢在不斷的摸索之后,找到了合適的解決方法。
EasyExcel對(duì)比其他框架
平常用poi讀取excel數(shù)據(jù)量少,加上EasyExcel讀取Excel有點(diǎn)復(fù)雜,所以一直也沒(méi)在項(xiàng)目中使用EasyExcel,直到有一回要讀取的數(shù)據(jù)量太大,使用poi讀取Excel在創(chuàng)建Workbook -> WorkbookFactory.create(inputStream) 時(shí)就異常了,分配很多內(nèi)存也不好使,所以放棄使用poi轉(zhuǎn)使用EasyExcel。
Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個(gè)嚴(yán)重的問(wèn)題就是非常的耗內(nèi)存,poi有一套SAX模式的API可以一定程度的解決一些內(nèi)存溢出的問(wèn)題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓后存儲(chǔ)都是在內(nèi)存中完成的,內(nèi)存消耗依然很大。easyexcel重寫了poi對(duì)07版Excel的解析,能夠原本一個(gè)3M的excel用POI sax依然需要100M左右內(nèi)存降低到幾M,并且再大的excel不會(huì)出現(xiàn)內(nèi)存溢出,03版依賴POI的sax模式。
在上層做了模型轉(zhuǎn)換的封裝,讓使用者更加簡(jiǎn)單方便 --EasyExcel
使用EasyExcel讀取Excel時(shí)一直在想如何簡(jiǎn)化讀取方式,不用讀取每個(gè)Excel都創(chuàng)建一個(gè)XXDataListene監(jiān)聽(tīng)器類,剛開(kāi)始想,把DataListener加上泛型,共用一個(gè)DataListener,但是還涉及到如何傳遞Dao和每個(gè)Dao如何保存數(shù)據(jù),而且保存數(shù)據(jù)前可能還需要對(duì)數(shù)據(jù)進(jìn)行不同的處理。
EasyExcel的編程模式
EasyExcel開(kāi)源挺久了,但使用上感覺(jué)有點(diǎn)讓人望而生怯,剛開(kāi)始看官方文檔上讀取Excel挺簡(jiǎn)單的,只需要一行代碼,繼續(xù)細(xì)看的話還需要?jiǎng)?chuàng)建一個(gè)回調(diào)監(jiān)聽(tīng)器,有點(diǎn)復(fù)雜呀(每個(gè)Excel都需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的回調(diào)監(jiān)聽(tīng)器類)。
EasyExcel讀取的指定位置
要開(kāi)始讀取數(shù)據(jù),第8行才是真正的數(shù)據(jù),直接上代碼,headRowNumber(),不寫默認(rèn)是1,即就是從第二行開(kāi)始讀數(shù)據(jù)。
/**
* 讀取文件信息數(shù)據(jù)
* @param filePath
* @param headNum
*/
public ContactInfoExcelDataListener read(String filePath , int headNum){
EasyExcel.read(filePath, this).head(ContactInfoExcelEntity.class).autoCloseStream(true
).autoTrim(true).ignoreEmptyRow(true).sheet()
// 這里可以設(shè)置1,因?yàn)轭^就是一行。如果多行頭,可以設(shè)置其他值。不傳入也可以,因?yàn)槟J(rèn)會(huì)根據(jù)DemoData 來(lái)解析,他沒(méi)有指定頭,也就是默認(rèn)1行
.headRowNumber(Math.max(headNum,NumberUtils.BYTE_ZERO)).doRead();
return this;
}
/**
* 讀取文件信息數(shù)據(jù)
* @param filePath
*/
public ContactInfoExcelDataListener read(String filePath){
EasyExcel.read(filePath, this).head(ContactInfoExcelEntity.class).autoCloseStream(true).autoTrim(true).ignoreEmptyRow(true).sheet()
// 這里可以設(shè)置1,因?yàn)轭^就是一行。如果多行頭,可以設(shè)置其他值。不傳入也可以,因?yàn)槟J(rèn)會(huì)根據(jù)DemoData 來(lái)解析,他沒(méi)有指定頭,也就是默認(rèn)1行
.doRead();
return this;
}
/**
* 讀取文件信息數(shù)據(jù)
* @param inputStream
* @param headNum
*/
public ContactInfoExcelDataListener read(InputStream inputStream, int headNum){
EasyExcel.read(inputStream, this).head(ContactInfoExcelEntity.class).autoCloseStream(true).autoTrim(true).ignoreEmptyRow(true).sheet()
// 這里可以設(shè)置1,因?yàn)轭^就是一行。如果多行頭,可以設(shè)置其他值。不傳入也可以,因?yàn)槟J(rèn)會(huì)根據(jù)DemoData 來(lái)解析,他沒(méi)有指定頭,也就是默認(rèn)1行
.headRowNumber(Math.max(headNum,NumberUtils.BYTE_ZERO)).doRead();
return this;
}
導(dǎo)入數(shù)據(jù)的流程
基本都會(huì)走到這里,全部放權(quán)交接給invoke方法,并且巧用作為我們鎖初始化操作的控制賦值,切記如果headNum = 0 此方法很有可能不會(huì)觸發(fā),慎用!
表頭校驗(yàn)
目前只是實(shí)現(xiàn)了相關(guān)的單節(jié)點(diǎn)同步鎖,如果未來(lái)擴(kuò)展了相關(guān)的分布式節(jié)點(diǎn),需要采用分布式鎖機(jī)制進(jìn)行控制!鎖范圍需要進(jìn)行控制
invokeHeadMap()方法
/**
* 調(diào)用頭部
* @param map
* @param analysisContext
*/
@Override
public void invokeHead(Map<Integer, CellData> map, AnalysisContext analysisContext) {
log.info("【start read the excel head data】:{}",map);// 判斷標(biāo)記頭是否存在
try {
int titleRows = map.size();
// 頭部的中斷處理機(jī)制!
failureDataCount = preValidate?orginalHead.size() != titleRows?NumberUtils.INTEGER_ONE:
NumberUtils.BYTE_ZERO:NumberUtils.BYTE_ZERO;
// 進(jìn)行置位
if(preValidate && (failureDataCount.intValue() == NumberUtils.INTEGER_ONE)){
causeByHeadFormatAbort = Boolean.TRUE;
}
if(!isMockFlag) {
// TODO 基本不會(huì)走到這里:一般我們?nèi)绻枰梢允褂么朔椒ㄗ鳛槌跏蓟Y源使用的目的!
//Preconditions.checkNotNull(clueLogic,"not support clueLogic is inject this class subject!");
if (Objects.isNull(clueLogic)) {
clueLogic = SpringUtils.getBean(ClueLogic.class);
}
customerImportVO = new CustomerImportVO();
// 此部分主要是為了減少不必要的內(nèi)存空間的申請(qǐng)
tempDataList = Lists.newArrayListWithExpectedSize(batchSizeUnit);
}
// syncLockController.lock();
} catch (Exception e) {
log.error("invoke the analysis the title head info data is failure!",e);
throw new UnsupportedOperationException("invoke the analysis the title head info data is failure!",e);
}
log.info("【finished read the excel head data】");
}
數(shù)據(jù)處理
invoke()方法
一條一條數(shù)據(jù)解析 invoke()方法 ,方法里面是我業(yè)務(wù)邏輯,數(shù)據(jù)校驗(yàn)。invoke 就是每行具體的數(shù)據(jù)值文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-811190.html
/**
* 調(diào)用操作處理控制機(jī)制
* @param excelEntity
* @param context
*/
@Override
public void invoke(ContactInfoExcelEntity excelEntity, AnalysisContext context) {
log.info("----【start read the excel main data:{}】----",excelEntity);
if(batchSizeUnit <= tempDataList.size()){
CustomerImportVO customerImportVO = clueLogic.startCallTaskProxy(contactInfoImportParam,tempDataList);
// 合并計(jì)算結(jié)果->更新為最新的結(jié)果
this.customerImportVO.merge(customerImportVO);
tempDataList.clear();
tempDataList = Lists.newArrayListWithExpectedSize(batchSizeUnit);
}else{
tempDataList.add(excelEntity);
}
log.info("【finished read the excel main data】");
}
執(zhí)行中斷
hasNextdoAfterAllAnalysed()方法
/**
* 是否擁有下一次執(zhí)行
* [@param](https://my.oschina.net/u/2303379) context
* [@return](https://my.oschina.net/u/556800)
*/
[@Override](https://my.oschina.net/u/1162528)
public boolean hasNext(AnalysisContext context) {
return causeByHeadFormatAbort?Boolean.FALSE:isSupportAbort? failureDataCount <= 0 :Boolean.TRUE;
}
數(shù)據(jù)完成
doAfterAllAnalysed()方法
所有數(shù)據(jù)解析完, doAfterAllAnalysed()方法,里面寫的有保存數(shù)據(jù)方法。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-811190.html
/**
* 執(zhí)行結(jié)束的回調(diào)機(jī)制
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
log.info("【doAfterAllAnalysed the process】");
try {
CustomerImportVO customerImportVO = clueLogic.startCallTaskProxy(contactInfoImportParam,tempDataList);
this.customerImportVO.merge(customerImportVO);
finisheDataResult = Boolean.TRUE;
}catch (Exception e){
log.error("execute finially the flush data is failure!");
//TODO 收尾的數(shù)據(jù)信息如何做到一致性和完成補(bǔ)償!
finisheDataResult = Boolean.FALSE;
} finally {
tempDataList.clear();
// syncLockController.unlock();
}
}
總結(jié)一下
- 快速讀寫:EasyExcel 支持 Excel 2003 和 Excel 2007 格式,并提供高效的讀寫性能。它使用了 NIO(新輸入/輸出)技術(shù),使得讀寫操作更加快速。
- 簡(jiǎn)單易用:EasyExcel 的 API 設(shè)計(jì)簡(jiǎn)潔明了,易于使用。開(kāi)發(fā)者只需編寫少量代碼,即可完成 Excel 文件的讀寫操作。它還支持鏈?zhǔn)骄幊?,使代碼更加簡(jiǎn)潔。
- 支持自定義:EasyExcel 提供了豐富的自定義選項(xiàng),允許開(kāi)發(fā)者根據(jù)需要調(diào)整 Excel 文件的格式、樣式等。它還支持自定義公式、條件格式等功能,滿足各種業(yè)務(wù)需求。
- 靈活的配置:EasyExcel 支持多種配置方式,如屬性配置、注解配置等。開(kāi)發(fā)者可以根據(jù)項(xiàng)目需求選擇合適的配置方式,使得 Excel 文件的處理更加靈活。
到了這里,關(guān)于【Alibaba工具型技術(shù)系列】「EasyExcel技術(shù)專題」實(shí)戰(zhàn)研究一下 EasyExcel 如何從指定文件位置進(jìn)行讀取數(shù)據(jù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!