基于Java8實(shí)現(xiàn)提高Excel讀寫(xiě)效率
在POI的使用過(guò)程中,對(duì)大多數(shù)API User來(lái)說(shuō)經(jīng)常面臨兩個(gè)問(wèn)題,這也是GridExcel致力解決的問(wèn)題。
問(wèn)題1. 僅使用簡(jiǎn)單的導(dǎo)入導(dǎo)出功能,但每次業(yè)務(wù)的數(shù)據(jù)對(duì)象結(jié)構(gòu)不同,需要重新編寫(xiě)處理方法,很麻煩!
解決方法
將Excel處理邏輯抽取出來(lái),封裝成工具類。
封裝條件
與大多數(shù)Java API一樣,POI把更多的精力放在高級(jí)功能的處理上,比如Formula(公式)、Conditional Formatting(條件格式)、Zoom(縮放)等。對(duì)于僅僅做數(shù)據(jù)導(dǎo)入導(dǎo)出功能的API User,很少使用這些高級(jí)特性,這允許API用戶對(duì)POI的使用進(jìn)行簡(jiǎn)單的封裝。
封裝方式
無(wú)論是讀是寫(xiě),我們都需要解決Excel中的Columns(列)與Java數(shù)據(jù)對(duì)象Fields(字段)的映射關(guān)系,將這種映射關(guān)系作為參數(shù)(Map對(duì)象HashMap或LinkedHashMap),傳遞給工具類。
對(duì)于Columns不難理解,它可以是有序的數(shù)字或字母,也可以是其它字符串用來(lái)作為首行,表示該列數(shù)據(jù)的含義。
對(duì)于Fields,它的處理需要兼容復(fù)雜情況,如下:
- 查詢字段時(shí)出現(xiàn)異常
- 字段或單元格的值為null
- 該列的值可能對(duì)應(yīng)關(guān)聯(lián)對(duì)象、甚至是關(guān)聯(lián)集合中的某個(gè)字段值
- 字段或單元格的值需要做特殊處理,例如value == true?完成:失??;
反射
首先想到,也是大多數(shù)封裝者都在使用的方式是就是Reflection API,從上文 函數(shù)編程 章節(jié)我們了解到,反射重量級(jí),會(huì)降低代碼的性能,同時(shí)對(duì)復(fù)雜情況的處理支持性不夠好。
反射+注解
這種方式可以更好的支持復(fù)雜情況,但是反射依然會(huì)降低性能,同時(shí)注解對(duì)數(shù)據(jù)對(duì)象會(huì)造成代碼侵入,而且對(duì)該工具類封裝者的其他使用者無(wú)疑會(huì)增加學(xué)習(xí)成本。
匿名內(nèi)部類
這種方式也可以很好的支持復(fù)雜情況,但是使用匿名內(nèi)部類的語(yǔ)法顯然患有“垂直問(wèn)題”(這意味著代碼需要太多的線條來(lái)表達(dá)基本概念),太過(guò)冗雜。至于性能,應(yīng)該也不如直接傳遞函數(shù)來(lái)的快吧。
函數(shù)接口(Lambda)
這種方式是基于第5條方法調(diào)用的字節(jié)碼指令invokeDynamic實(shí)現(xiàn)的,直接傳遞函數(shù)代碼塊,很好的支持復(fù)雜情況,性能較高,代碼編寫(xiě)更簡(jiǎn)單結(jié)構(gòu)更加簡(jiǎn)潔,而且對(duì)數(shù)據(jù)對(duì)象代碼零侵入。
當(dāng)然如果你還沒(méi)有使用Java1.8或更高版本,那么你可以參考匿名內(nèi)部類或反射+注解,不過(guò)還是推薦反射+注解,Alibaba/easyexcel【https://github.com/alibaba/easyexcel】對(duì)你來(lái)說(shuō)會(huì)是不錯(cuò)的選擇。
問(wèn)題2. Excel導(dǎo)入或?qū)С鰯?shù)據(jù)量比較大,造成內(nèi)存溢出或頻繁的Full GC,該如何解決?
解決方法
- 讀Excel —— eventmodel
- 寫(xiě)Excel —— streaming.SXSSFWorkbook
原理
POI的使用對(duì)我們來(lái)說(shuō)很常見(jiàn),對(duì)下面兩個(gè)概念應(yīng)該并不陌生:
- HSSFWorkbook(處理97(-2007) 的.xls)
- XSSFWorkbook(處理2007 OOXML (.xlsx) )
但是對(duì)于eventmodel和streaming.SXSSFWorkbook就很少接觸了,它們是POI提供的專門用來(lái)解決內(nèi)存占用問(wèn)題的low level API(低級(jí)API),使用它們可以讀寫(xiě)數(shù)據(jù)量非常大的Excel,同時(shí)可以避免內(nèi)存溢出或頻繁的Full GC?!緃ttps://poi.apache.org/components/spreadsheet/how-to.html】
eventmodel,用來(lái)讀Excel,并沒(méi)有將Excel整個(gè)加載到內(nèi)存中,而是允許用戶從InputStream每讀取一些信息,就交給回調(diào)函數(shù)或監(jiān)聽(tīng)器,至于丟棄,存儲(chǔ)還是怎么處理這些內(nèi)容,都交由用戶。
streaming.SXSSFWorkbook,用來(lái)寫(xiě)Excel(是對(duì)XSSFWorkbook的封裝,僅支持.xlsx),通過(guò)滑動(dòng)窗口來(lái)實(shí)現(xiàn),只在內(nèi)存中保留滑動(dòng)窗口允許存在的行數(shù),超出的行Rows被寫(xiě)出到臨時(shí)文件,當(dāng)調(diào)用write(OutputStream stream)方法寫(xiě)出內(nèi)容時(shí),再直接從臨時(shí)內(nèi)存寫(xiě)出到目標(biāo)OutputStream。SXSSFWorkbook的使用會(huì)產(chǎn)生一些局限性。
- Only a limited number of rows are accessible at a point in time.
- Sheet.clone() is not supported.
- Formula evaluation is not supported
解決途徑
https://github.com/liuhuagui/gridexcel 基于Java函數(shù)編程(Lambda),支持流式API,使用環(huán)境Java1.8或更高,學(xué)習(xí)成本:
Lambda
https://github.com/alibaba/easyexcel 基于反射+注解+監(jiān)聽(tīng)器,使用環(huán)境Java1.6或以上,學(xué)習(xí)成本:模型注解
實(shí)際上POI官網(wǎng)已經(jīng)給了用戶使用示例,而上述兩個(gè)工具都只是做了自己的封裝實(shí)現(xiàn),使用者只需要拿來(lái)用就好。
快速使用
<dependency> <groupId>com.github.liuhuagui</groupId> <artifactId>gridexcel</artifactId> <version>2.2</version> </dependency>
GridExcel.java
GridExcel.java提供了多種靜態(tài)方法,可以直接使用,具體式例可參考測(cè)試代碼(提供了測(cè)試數(shù)據(jù)和測(cè)試文件):
https://github.com/liuhuagui/gridexcel/blob/master/src/test/java/ReadTest.java
https://github.com/liuhuagui/gridexcel/blob/master/src/test/java/WriteTest.java
流式API
/** * 業(yè)務(wù)邏輯處理方式三選一: * 1.啟用windowListener,并將業(yè)務(wù)邏輯放在該函數(shù)中。 * 2.不啟用windowListener,使用get()方法取回全部數(shù)據(jù)集合,做后續(xù)處理。 * 3.readFunction函數(shù),直接放在函數(shù)中處理 或 使用final or effective final的局部變量存放這寫(xiě)數(shù)據(jù),做后續(xù)處理。 * 注意:使用EventModel時(shí)readFunction函數(shù)的輸入為每行的cell值集合List<String>。 * @throws Exception */ @Test public void readXlsxByEventModel() throws Exception { InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("2007.xlsx"); GridExcel.readByEventModel(resourceAsStream,TradeOrder.class,ExcelType.XLSX) .window(2,ts -> System.out.println(JSON.toJSONString(ts)))//推薦在這里執(zhí)行自己的業(yè)務(wù)邏輯 .process(cs ->{ TradeOrder tradeOrder = new TradeOrder(); tradeOrder.setTradeOrderId(Long.valueOf(cs.get(0))); Consultant consultant = new Consultant(); consultant.setConsultantName(cs.get(3)); tradeOrder.setConsultant(consultant); tradeOrder.setPaymentRatio(cs.get(16)); return tradeOrder; },1); } /** * 使用Streaming UserModel寫(xiě)出數(shù)據(jù)到Excel * @throws Exception */ @Test public void writeExcelByStreaming() throws Exception { GridExcel.writeByStreaming(TradeOrder.class) .head(writeFunctionMap())//對(duì)象字段到Excel列的映射 .createSheet() .process(MockData.data())//模擬數(shù)據(jù)。在這里設(shè)置業(yè)務(wù)數(shù)據(jù)集合。 .write(FileUtils.openOutputStream(new File("/excel/test.xlsx"))); }
ReadExcel
ReadExcelByUserModel
Use user model to read excel file. userModel ——
缺點(diǎn):內(nèi)存消耗大,會(huì)將excel信息全部加載到內(nèi)存再進(jìn)行處理。
優(yōu)點(diǎn):現(xiàn)成的API,使用和理解更簡(jiǎn)單。
使用場(chǎng)景:可以處理數(shù)據(jù)量較小的Excel。
ReadExcelByEventModel
Use event model to read excel file. eventModel ——
缺點(diǎn):沒(méi)有現(xiàn)成的API,使用和理解較為復(fù)雜,適合中高級(jí)程序員(GridExcel的目標(biāo)之一就是讓EventModel的使用變得簡(jiǎn)單)
優(yōu)點(diǎn):非常小的內(nèi)存占用,并沒(méi)有在一開(kāi)始就將所有內(nèi)容加載到內(nèi)存中,而是把主體內(nèi)容的處理(存儲(chǔ),使用,丟棄)都交給了用戶,用戶可以自定義監(jiān)聽(tīng)函數(shù)來(lái)處理這些內(nèi)容。
使用場(chǎng)景:可以處理較大數(shù)據(jù)量的Excel,避免OOM和頻繁FullGC
WriteExcel
WriteExcelByUserModel
Use user model to write excel file. userModel ——
缺點(diǎn):會(huì)將產(chǎn)生的spreadsheets對(duì)象整個(gè)保存在內(nèi)存中,所以write Excel的大小受到堆內(nèi)存(Heap space)大小限制。
優(yōu)點(diǎn):使用和理解更簡(jiǎn)單。
使用場(chǎng)景:可以寫(xiě)出數(shù)據(jù)量較小的Excel。
WriteExcelByStreaming
Use API-compatible streaming extension of XSSF to write very large excel file. streaming userModel——
缺點(diǎn):
- 僅支持XSSF;
- Sheet.clone() is not supported;
- Formula evaluation is not supported;
- Only a limited number of rows are accessible at a point in time.
優(yōu)點(diǎn):通過(guò)滑動(dòng)窗口來(lái)實(shí)現(xiàn),內(nèi)存中只保留指定size of rows的內(nèi)容,超出部分被寫(xiě)出到臨時(shí)文件,write Excel的大小不再受到堆內(nèi)存(Heap space)大小限制。
使用場(chǎng)景:可以寫(xiě)出非常大的Excel。
Issues
在使用工具過(guò)程中出現(xiàn)問(wèn)題,有功能添加或改動(dòng)需求的可以向作者提Issue:https://github.com/liuhuagui/gridexcel/issues
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java中利用Alibaba開(kāi)源技術(shù)EasyExcel來(lái)操作Excel表的示例代碼
- Java 設(shè)置Excel條件格式示例代碼(高亮條件值、應(yīng)用單元格值/公式/數(shù)據(jù)條等類型)
- Java中Easypoi實(shí)現(xiàn)excel多sheet表導(dǎo)入導(dǎo)出功能
- Java利用POI讀寫(xiě)Excel文件工具類
- java讀取簡(jiǎn)單excel通用工具類
- java讀取excel表格的方法
- java 使用poi 導(dǎo)入Excel數(shù)據(jù)到數(shù)據(jù)庫(kù)的步驟
- Java 在Excel中添加分離型餅圖、環(huán)形圖的方法
- vue通過(guò)接口直接下載java生成好的Excel表格案例
- Java 導(dǎo)出excel進(jìn)行換行的案例
- Java 如何快速,優(yōu)雅的實(shí)現(xiàn)導(dǎo)出Excel
相關(guān)文章
java判斷域名無(wú)法訪問(wèn)自行訪問(wèn)下一條
這篇文章主要為大家介紹了java實(shí)現(xiàn)判斷域名無(wú)法訪問(wèn)的時(shí)候自行訪問(wèn)下一條域名示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12SpringBoot整合Flink CDC實(shí)現(xiàn)實(shí)時(shí)追蹤mysql數(shù)據(jù)變動(dòng)
我們將整合Spring Boot和Apache Flink CDC(Change Data Capture)來(lái)實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)追蹤,下面是一個(gè)基本的實(shí)踐流程代碼,包括搭建Spring Boot項(xiàng)目、整合Flink CDC以及實(shí)現(xiàn)數(shù)據(jù)變動(dòng)的實(shí)時(shí)追蹤,需要的朋友可以參考下2024-07-07一文教你使用Java?Calendar類進(jìn)行日期計(jì)算
在日常開(kāi)發(fā)中,我們常常需要進(jìn)行日期計(jì)算,比如計(jì)算兩個(gè)日期之間的天數(shù)、月數(shù),在Java中,我們可以使用Java?Calendar類來(lái)進(jìn)行日期計(jì)算,下面就跟隨小編一起來(lái)學(xué)習(xí)一下吧2023-10-10MyBatis實(shí)現(xiàn)數(shù)據(jù)庫(kù)類型和Java類型的轉(zhuǎn)換
MyBatis 在處理數(shù)據(jù)庫(kù)查詢結(jié)果或傳遞參數(shù)時(shí),需要將數(shù)據(jù)庫(kù)類型與 Java 類型之間進(jìn)行轉(zhuǎn)換,本文就給大家介紹MyBatis如何實(shí)現(xiàn)數(shù)據(jù)庫(kù)類型和 Java 類型的轉(zhuǎn)換的,需要的朋友可以參考下2024-09-09關(guān)于@PostConstruct、afterPropertiesSet和init-method的執(zhí)行順序
這篇文章主要介紹了關(guān)于@PostConstruct、afterPropertiesSet和init-method的執(zhí)行順序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09