天天看點

easyexcel的源碼簡單分析

文章目錄

  • ​​讀的流程圖​​
  • ​​邏輯分析​​
  • ​​1.EasyExcel​​
  • ​​2.EasyExcelFactory​​
  • ​​ExcelReaderSheetBuilder​​
  • ​​ExcelReader​​
  • ​​ExcelAnalyserImpl​​
  • ​​XlsxSaxAnalyser​​
  • ​​DefaultAnalysisEventProcessor​​
  • ​​DemoDatalistener的invoke()方法被調用​​
  • ​​傳回到XlsxSaxAnalyser​​
  • ​​DefaultAnalysisEventProcessor的endSheet()​​
  • ​​DemoDataistener的doAfterAllAnalysed()​​
  • ​​備注:官方給的詳細參數介紹​​
  • ​​關于常見類解析​​
  • ​​讀​​
  • ​​注解​​
  • ​​參數​​
  • ​​通用參數​​
  • ​​ReadWorkbook(了解成excel對象)參數​​
  • ​​ReadSheet(就是excel的一個Sheet)參數​​
  • ​​寫​​
  • ​​注解​​
  • ​​參數​​
  • ​​通用參數​​
  • ​​WriteWorkbook(了解成excel對象)參數​​
  • ​​WriteSheet(就是excel的一個Sheet)參數​​
  • ​​WriteTable(就把excel的一個Sheet,一塊區域看一個table)參數​​
  • ​​10M以上檔案讀取說明​​
  • ​​如果對讀取效率感覺還能接受,就用預設的,永久占用(單個excel讀取整個過程)一般不會超過50M(大機率就30M),剩下臨時的GC會很快回收​​
  • ​​預設大檔案處理​​
  • ​​根據實際需求配置記憶體​​
  • ​​如果最大檔案條數也就十幾二十萬,然後excel也就是十幾二十M,而且不會有很高的并發,并且記憶體也較大​​
  • ​​對并發要求較高,而且都是經常有超級大檔案​​
  • ​​關于maxCacheActivateSize 也就是前面第二個參數的詳細說明​​
  • ​​如何判斷 maxCacheActivateSize是否需要調整​​

最近公司用到easyexcel,跟了下源碼,簡單了解的部分流程。做一下筆記,友善以後查詢。

本人主要根據官方給的最簡單的read demo的調試下了:

讀的流程圖

流程圖大體如下:

easyexcel的源碼簡單分析
  • 讀的代碼
ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
       ReadSheet readSheet = EasyExcel.readSheet(0).build();
       excelReader.read(readSheet);
       // 這裡千萬别忘記關閉,讀的時候會建立臨時檔案,到時磁盤會崩的
       excelReader.finish();      

邏輯分析

1.EasyExcel

easyexcel就是一個門面他是一個空的類,主要幹活的是他的父類:

public class EasyExcel extends EasyExcelFactory {
}      

2.EasyExcelFactory

作用: ExcelWriterBuilder 建構出一個 ReadWorkbook WriteWorkbook,可以了解成一個excel對象,一個excel隻要建構.

繼續看這行代碼: EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();

進入read:

public static ExcelReaderBuilder read(String pathName, Class head, ReadListener readListener) {
        ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
        excelReaderBuilder.file(pathName);
        if (head != null) {
            excelReaderBuilder.head(head);
        }
        if (readListener != null) {
            excelReaderBuilder.registerReadListener(readListener);
        }
        return excelReaderBuilder;
    }      

主要幹了以下幾件事:

  • 1.初始化了​

    ​ExcelReaderBuilder​

    ​這個對象:
  • 2将Demo.class對應的标題頭資訊放入​

    ​BasicParameter​

    ​這個類相關子中。
  • 3将Demo.controller對應的标題頭資訊放入​

    ​ReadBasicParameter​

    ​這個類中。

ExcelReaderSheetBuilder

繼續看這行代碼:

​ReadSheet readSheet = EasyExcel.readSheet(0).build();​

幹了這件事:

public static ExcelReaderSheetBuilder readSheet(Integer sheetNo) {

return readSheet(sheetNo, null);

}

ExcelWriterSheetBuilder 建構出一個 ReadSheet WriteSheet對象,可以了解成excel裡面的一頁,每一頁都要建構一個.

ExcelReader

這個類是整個個讀的入口類:

看這個代碼

.read(readSheet);      

源碼調試:

public ExcelReader read(ReadSheet... readSheet) {
        return read(Arrays.asList(readSheet));
    }      

繼續點選read方法

public ExcelReader read(List<ReadSheet> readSheetList) {
        excelAnalyser.analysis(readSheetList, Boolean.FALSE);
        return this;
 }      

最主要的是這個類:excelAnalyser

easyexcel的源碼簡單分析

XlsxSaxAnalyser負責解析excel的每個格子的資料。

DefaultAnalysisEventProcessor會擷取解析到的資料,并各個readListener的子類比如我們自定義的demoDataListener的invoke方法,然後由各個listener對資料進行處理。

進入analysis:

ExcelAnalyserImpl

easyexcel的源碼簡單分析

點選進入:

XlsxSaxAnalyser

easyexcel的源碼簡單分析

看下解析每行資料的代碼:

會進入

easyexcel的源碼簡單分析

然後進入parseXmlSource方法:

easyexcel的源碼簡單分析

DefaultAnalysisEventProcessor

easyexcel的源碼簡單分析

dealData():

這個是調用自定義監聽處理每行資料的invoke的入口:

easyexcel的源碼簡單分析

DemoDatalistener的invoke()方法被調用

easyexcel的源碼簡單分析

傳回到XlsxSaxAnalyser

處理完每行之後

easyexcel的源碼簡單分析

進入endSheet()方法:

DefaultAnalysisEventProcessor的endSheet()

easyexcel的源碼簡單分析

for循環裡面是調用自定義監聽的lDemoDataistener的

DemoDataistener的doAfterAllAnalysed()

easyexcel的源碼簡單分析

自此,走完了所有的流程。

備注:官方給的詳細參數介紹

關于常見類解析

  • EasyExcel 入口類,用于建構開始各種操作
  • ExcelReaderBuilder ExcelWriterBuilder 建構出一個 ReadWorkbook WriteWorkbook,可以了解成一個excel對象,一個excel隻要建構一個
  • ExcelReaderSheetBuilder ExcelWriterSheetBuilder 建構出一個 ReadSheet WriteSheet對象,可以了解成excel裡面的一頁,每一頁都要建構一個
  • ReadListener 在每一行讀取完畢後都會調用ReadListener來處理資料
  • WriteHandler 在每一個操作包括建立單元格、建立表格等都會調用WriteHandler來處理資料
  • 所有配置都是繼承的,Workbook的配置會被Sheet繼承,是以在用EasyExcel設定參數的時候,在EasyExcel…sheet()方法之前作用域是整個sheet,之後針對單個sheet

注解

  • ​ExcelProperty​

    ​ 指定目前字段對應excel中的那一列。可以根據名字或者Index去比對。當然也可以不寫,預設第一個字段就是index=0,以此類推。千萬注意,要麼全部不寫,要麼全部用index,要麼全部用名字去比對。千萬别三個混着用,除非你非常了解源代碼中三個混着用怎麼去排序的。
  • ​ExcelIgnore​

    ​ 預設所有字段都會和excel去比對,加了這個注解會忽略該字段
  • ​DateTimeFormat​

    ​​ 日期轉換,用​

    ​String​

    ​​去接收excel日期格式的資料會調用這個注解。裡面的​

    ​value​

    ​​參照​

    ​java.text.SimpleDateFormat​

  • ​NumberFormat​

    ​​ 數字轉換,用​

    ​String​

    ​​去接收excel數字格式的資料會調用這個注解。裡面的​

    ​value​

    ​​參照​

    ​java.text.DecimalFormat​

  • ​ExcelIgnoreUnannotated​

    ​​ 預設不加​

    ​ExcelProperty​

    ​ 的注解的都會參與讀寫,加了不會參與

參數

通用參數

​ReadWorkbook​

​​,​

​ReadSheet​

​ 都會有的參數,如果為空,預設使用上級。

  • ​converter​

    ​ 轉換器,預設加載了很多轉換器。也可以自定義。
  • ​readListener​

    ​ 監聽器,在讀取資料的過程中會不斷的調用監聽器。
  • ​headRowNumber​

    ​ 需要讀的表格有幾行頭資料。預設有一行頭,也就是認為第二行開始起為資料。
  • ​head​

    ​​ 與​

    ​clazz​

    ​二選一。讀取檔案頭對應的清單,會根據清單比對資料,建議使用class。
  • ​clazz​

    ​​ 與​

    ​head​

    ​二選一。讀取檔案的頭對應的class,也可以使用注解。如果兩個都不指定,則會讀取全部資料。
  • ​autoTrim​

    ​ 字元串、表頭等資料自動trim
  • ​password​

    ​ 讀的時候是否需要使用密碼
ReadWorkbook(了解成excel對象)參數
  • ​excelType​

    ​ 目前excel的類型 預設會自動判斷
  • ​inputStream​

    ​​ 與​

    ​file​

    ​​二選一。讀取檔案的流,如果接收到的是流就隻用,不用流建議使用​

    ​file​

    ​​參數。因為使用了​

    ​inputStream​

    ​​ easyexcel會幫忙建立臨時檔案,最終還是​

    ​file​

  • ​file​

    ​​ 與​

    ​inputStream​

    ​二選一。讀取檔案的檔案。
  • ​autoCloseStream​

    ​ 自動關閉流。
  • ​readCache​

    ​​ 預設小于5M用 記憶體,超過5M會使用​

    ​EhCache​

    ​,這裡不建議使用這個參數。
ReadSheet(就是excel的一個Sheet)參數
  • ​sheetNo​

    ​ 需要讀取Sheet的編碼,建議使用這個來指定讀取哪個Sheet
  • ​sheetName​

    ​ 根據名字去比對Sheet,excel 2003不支援根據名字去比對

注解

  • ​ExcelProperty​

    ​​ index 指定寫到第幾列,預設根據成員變量排序。​

    ​value​

    ​​指定寫入的名稱,預設成員變量的名字,多個​

    ​value​

    ​可以參照快速開始中的複雜頭
  • ​ExcelIgnore​

    ​ 預設所有字段都會寫入excel,這個注解會忽略這個字段
  • ​DateTimeFormat​

    ​​ 日期轉換,将​

    ​Date​

    ​​寫到excel會調用這個注解。裡面的​

    ​value​

    ​​參照​

    ​java.text.SimpleDateFormat​

  • ​NumberFormat​

    ​​ 數字轉換,用​

    ​Number​

    ​​寫excel會調用這個注解。裡面的​

    ​value​

    ​​參照​

    ​java.text.DecimalFormat​

  • ​ExcelIgnoreUnannotated​

    ​​ 預設不加​

    ​ExcelProperty​

    ​ 的注解的都會參與讀寫,加了不會參與

參數

通用參數

​WriteWorkbook​

​​,​

​WriteSheet​

​​ ,​

​WriteTable​

​都會有的參數,如果為空,預設使用上級。

  • ​converter​

    ​ 轉換器,預設加載了很多轉換器。也可以自定義。
  • ​writeHandler​

    ​​ 寫的處理器。可以實作​

    ​WorkbookWriteHandler​

    ​​,​

    ​SheetWriteHandler​

    ​​,​

    ​RowWriteHandler​

    ​​,​

    ​CellWriteHandler​

    ​,在寫入excel的不同階段會調用
  • ​relativeHeadRowIndex​

    ​ 距離多少行後開始。也就是開頭空幾行
  • ​needHead​

    ​ 是否導出頭
  • ​head​

    ​​ 與​

    ​clazz​

    ​二選一。寫入檔案的頭清單,建議使用class。
  • ​clazz​

    ​​ 與​

    ​head​

    ​二選一。寫入檔案的頭對應的class,也可以使用注解。
  • ​autoTrim​

    ​ 字元串、表頭等資料自動trim
WriteWorkbook(了解成excel對象)參數
  • ​excelType​

    ​​ 目前excel的類型 預設​

    ​xlsx​

  • ​outputStream​

    ​​ 與​

    ​file​

    ​二選一。寫入檔案的流
  • ​file​

    ​​ 與​

    ​outputStream​

    ​二選一。寫入的檔案
  • ​templateInputStream​

    ​ 模闆的檔案流
  • ​templateFile​

    ​ 模闆檔案
  • ​autoCloseStream​

    ​ 自動關閉流。
  • ​password​

    ​ 寫的時候是否需要使用密碼
  • ​useDefaultStyle​

    ​ 寫的時候是否是使用預設頭
WriteSheet(就是excel的一個Sheet)參數
  • ​sheetNo​

    ​ 需要寫入的編碼。預設0
  • ​sheetName​

    ​​ 需要些的Sheet名稱,預設同​

    ​sheetNo​

WriteTable(就把excel的一個Sheet,一塊區域看一個table)參數
  • ​tableNo​

    ​ 需要寫入的編碼。預設0

10M以上檔案讀取說明

03版沒有辦法處理,相對記憶體占用大很多。excel 07版本有個共享字元串​​共享字元串​​的概念,這個會非常占用記憶體,如果全部讀取到記憶體的話,大概是excel檔案的大小的3-10倍,是以easyexcel用存儲檔案的,然後再反序列化去讀取的政策來節約記憶體。當然需要通過檔案反序列化以後,效率會降低,大概降低30-50%(不一定,也看命中率,可能會超過100%)

如果對讀取效率感覺還能接受,就用預設的,永久占用(單個excel讀取整個過程)一般不會超過50M(大機率就30M),剩下臨時的GC會很快回收

預設大檔案處理

預設大檔案處理會自動判斷,共享字元串5M以下會使用記憶體存儲,大概占用15-50M的記憶體,超過5M則使用檔案存儲,然後檔案存儲也要設定多記憶體M用來存放臨時的共享字元串,預設20M。除了共享字元串占用記憶體外,其他占用較少,是以可以預估10M,是以預設大概30M就能讀取一個超級大的檔案。

根據實際需求配置記憶體

想自定義設定,首先要确定你大概願意花多少記憶體來讀取一個超級大的excel,比如希望讀取excel最多占用100M記憶體(是讀取過程中永久占用,新生代馬上回收的不算),那就設定使用檔案來存儲共享字元串的大小判斷為20M(小于20M存記憶體,大于存臨時檔案),然後設定檔案存儲時臨時共享字元串占用記憶體大小90M差不多

如果最大檔案條數也就十幾二十萬,然後excel也就是十幾二十M,而且不會有很高的并發,并且記憶體也較大

// 強制使用記憶體存儲,這樣大概一個20M的excel使用150M(很多臨時對象,是以100M會一直GC)的記憶體
// 這樣效率會比上面的複雜的政策高很多
   // 這裡再說明下 就是加了個readCache(new MapCache()) 參數而已,其他的參照其他demo寫 這裡沒有寫全 
  EasyExcel.read().readCache(new MapCache());      

對并發要求較高,而且都是經常有超級大檔案

// 第一個參數的意思是 多少M共享字元串以後 采用檔案存儲 機關MB 預設5M
// 第二個參數 檔案存儲時,記憶體存放多少M緩存資料 預設20M
// 比如 你希望用100M記憶體(這裡說的是解析過程中的永久占用,臨時對象不算)來解析excel,前面算過了 大概是 20M+90M 是以設定參數為:20 和 90 
   // 這裡再說明下 就是加了個readCacheSelector(new SimpleReadCacheSelector(5, 20))參數而已,其他的參照其他demo寫 這裡沒有寫全 
EasyExcel.read().readCacheSelector(new SimpleReadCacheSelector(5, 20));      

關于maxCacheActivateSize 也就是前面第二個參數的詳細說明

easyexcel在使用檔案存儲的時候,會把共享字元串拆分成1000條一批,然後放到檔案存儲。然後excel來讀取共享字元串大機率是按照順序的,是以預設20M的1000條的資料放在記憶體,命中後直接傳回,沒命中去讀檔案。是以不能設定太小,太小了,很難命中,一直去讀取檔案,太大了的話會占用過多的記憶體。

如何判斷 maxCacheActivateSize是否需要調整

開啟debug日志會輸出​

​Already put :4000000​

​​ 最後一次輸出,大概可以得出值為400W,然後看​

​Cache misses count:4001​

​​得到值為4K,400W/4K=1000 這代表已經​

​maxCacheActivateSize​

​ 已經非常合理了。如果小于500 問題就非常大了,500到1000 應該都還行。

個人微信公衆号:

搜尋: 怒放de每一天

不定時推送相關文章,期待和大家一起成長!!