1. 程式人生 > 程式設計 >基於Java8實現提高Excel讀寫效率

基於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,它的處理需要相容複雜情況,如下:

  • 查詢欄位時出現異常
  • 欄位或單元格的值為null
  • 該列的值可能對應關聯物件、甚至是關聯集合中的某個欄位值
  • 欄位或單元格的值需要做特殊處理,例如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官網已經給了使用者使用示例,而上述兩個工具都只是做了自己的封裝實現,使用者只需要拿來用就好。

快速使用

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

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<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)))//推薦在這裡執行自己的業務邏輯
       .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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。