Java8如何讓Excel的讀寫變得更加簡單高效

在POI的使用過程中,對大多數API User來說經常面臨兩個問題,這也是 GridExcel 致力解決的問題。

問題1. 僅使用簡單的導入導出功能,但每次業務的數據對象結構不同,需要重新編寫處理方法,很麻煩!
解決方法
將Excel處理邏輯抽取出來,封裝成工具類。

封裝條件
與大多數Java API一樣,POI把更多的精力放在高級功能的處理上,比如Formula(公式)、Conditional Formatting(條件格式)、Zoom(縮放)等。對於僅僅做數據導入導出功能的API User,很少使用這些高級特性,這允許API用戶對POI的使用進行簡單的封裝。

封裝方式
無論是讀是寫,我們都需要解決Excel中的Columns(列)與Java數據對象Fields(字段)的映射關係,將這種映射關係作爲參數(Map對象HashMap或LinkedHashMap),傳遞給工具類。

對於Columns不難理解,它可以是有序的數字或字母,也可以是其它字符串用來作爲首行,表示該列數據的含義。

對於Fields,它的處理需要兼容複雜情況,如下:

value == true?完成:失敗;
反射
首先想到,也是大多數封裝者都在使用的方式是就是 Reflection API ,從上文 函數編程 章節我們瞭解到,反射重量級,會降低代碼的性能,同時對複雜情況的處理支持性不夠好。

反射+註解
這種方式可以更好的支持複雜情況,但是反射依然會降低性能,同時註解對數據對象會造成代碼侵入,而且對該工具類封裝者的其他使用者無疑會增加學習成本。

匿名內部類
這種方式也可以很好的支持複雜情況,但是使用匿名內部類的語法顯然患有“垂直問題”(這意味着代碼需要太多的線條來表達基本概念),太過冗雜。

至於性能,應該也不如直接傳遞函數來的快吧。

函數接口(Lambda)
這種方式是基於第5條方法調用的字節碼指令 invokeDynamic 實現的,直接傳遞函數代碼塊,很好的支持複雜情況,性能較高,代碼編寫更簡單結構更加簡潔,而且對數據對象代碼零侵入。

當然如果你還沒有使用Java1.8或更高版本,那麼你可以參考 匿名內部類 或 反射+註解 ,不過還是推薦 反射+註解 , Alibaba/easyexcel【https://github.com/alibaba/easyexcel】 對你來說會是不錯的選擇。

問題2. Excel導入或導出數據量比較大,造成 內存溢出 或 頻繁的Full GC ,該如何解決?
解決方法
讀Excel —— eventmodel
寫Excel —— streaming.SXSSFWorkbook
原理
POI的使用對我們來說很常見,對下面兩個概念應該並不陌生:

HSSFWorkbook(處理97(-2007) 的.xls)
XSSFWorkbook(處理2007 OOXML (.xlsx) )
但是對於 eventmodel 和 streaming.SXSSFWorkbook 就很少接觸了,它們是POI提供的專門用來解決內存佔用問題的 low level API (低級API),使用它們可以讀寫數據量非常大的Excel,同時可以避免 內存溢出 或 頻繁的Full GC 。【 https://poi.apache.org/components/spreadsheet/how-to.html

eventmodel ,用來讀Excel,並沒有將Excel整個加載到內存中,而是允許用戶從 InputStream 每讀取一些信息,就交給 回調函數 或 監聽器 ,至於丟棄,存儲還是怎麼處理這些內容,都交由用戶。
streaming.SXSSFWorkbook ,用來寫Excel(是對XSSFWorkbook的封裝,僅支持.xlsx),通過 滑動窗口 來實現,只在內存中保留滑動窗口允許存在的行數,超出的行Rows被寫出到臨時文件,當調用 write(OutputStream stream) 方法寫出內容時,再直接從臨時內存寫出到目標 OutputStream 。 SXSSFWorkbook 的使用會產生一些侷限性。
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函數編程(Lambda),支持流式API,使用環境Java1.8或更高,學習成本:Lambda
https://github.com/alibaba/easyexcel 基於反射+註解+監聽器,使用環境Java1.6或以上,學習成本:模型註解
實際上POI官網已經給了用戶使用示例,而上述兩個工具都只是做了自己的封裝實現,使用者只需要拿來用就好。

快速使用

<groupId>com.github.liuhuagui</groupId>
<artifactId>gridexcel</artifactId>
<version>2.2</version>


GridExcel.java
GridExcel.java提供了多種靜態方法,可以直接使用,具體式例可參考測試代碼(提供了測試數據和測試文件):

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
/**

  • 業務邏輯處理方式三選一:
  • 1.啓用windowListener,並將業務邏輯放在該函數中。
  • 2.不啓用windowListener,使用get()方法取回全部數據集合,做後續處理。
  • 3.readFunction函數,直接放在函數中處理 或 使用final or effective final的局部變量存放這寫數據,做後續處理。
  • 注意:使用EventModel時readFunction函數的輸入爲每行的cell值集合List。
  • @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)))//推薦在這裏執行自己的業務邏輯
         .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寫出數據到Excel
  • @throws Exception
    */

@Test
public void writeExcelByStreaming() throws Exception {

 GridExcel.writeByStreaming(TradeOrder.class)
         .head(writeFunctionMap())//對象字段到Excel列的映射
         .createSheet()
         .process(MockData.data())//模擬數據。在這裏設置業務數據集合。
         .write(FileUtils.openOutputStream(new File("/excel/test.xlsx")));

}
ReadExcel
ReadExcelByUserModel
Use user model to read excel file. userModel ——

缺點 :內存消耗大,會將excel信息全部加載到內存再進行處理。
優點 :現成的API,使用和理解更簡單。
使用場景 :可以處理數據量較小的Excel。
ReadExcelByEventModel
Use event model to read excel file. eventModel ——

缺點 :沒有現成的API,使用和理解較爲複雜,適合中高級程序員(GridExcel的目標之一就是讓EventModel的使用變得簡單)
優點 :非常小的內存佔用,並沒有在一開始就將所有內容加載到內存中,而是把主體內容的處理(存儲,使用,丟棄)都交給了用戶,用戶可以自定義監聽函數來處理這些內容。
使用場景 :可以處理較大數據量的Excel,避免OOM和頻繁FullGC
WriteExcel
WriteExcelByUserModel
Use user model to write excel file. userModel ——

缺點 :會將產生的spreadsheets對象整個保存在內存中,所以write Excel的大小受到堆內存(Heap space)大小限制。
優點 :使用和理解更簡單。
使用場景 :可以寫出數據量較小的Excel。
WriteExcelByStreaming
Use API-compatible streaming extension of XSSF to write very large excel file. streaming userModel——

缺點 :
僅支持XSSF;
Sheet.clone() is not supported;
Formula evaluation is not supported;
Only a limited number of rows are accessible at a point in time.

優點 :通過滑動窗口來實現,內存中只保留指定size of rows的內容,超出部分被寫出到臨時文件,write Excel的大小不再受到堆內存(Heap space)大小限制。
使用場景 :可以寫出非常大的Excel。
Issues
在使用工具過程中出現問題,有功能添加或改動需求的可以向作者提Issue: https://github.com/liuhuagui/gridexcel/issues

比如說,想要增加對首行以外的行列做樣式擴展 需要java學習路線圖的私信筆者“java”領取哦!另外喜歡這篇文章的可以給筆者點個贊同,關注一下,每天都會分享Java相關文章!還有不定時的福利贈送,包括整理的學習資料,面試題,源碼等~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章