easyexcel--解決poi大文件發生OOM問題

問題復現

工作中,項目裏的導入功能採用了poi讀取然後進行業務操作,在導入50M文件時發生了OOM報錯信息,以下是本地復現的錯誤信息(由於環境不一樣,本地導入14M的文件就已出現錯誤)

img

究其原因

項目中使用WorkBook這個類處理文件,這會先把文件中cell讀到內存當中,當數據量比較大的時候就會產生java.lang.OutOfMemoryError: Java heap space錯誤。

看一下導入過程中內存的消耗情況

img

可以看出,導入過程內存消耗越來越大,直至拋出異常

easyExcel要去解決的問題

Excel讀寫時候內存溢出

雖然POI是目前使用最多的用來做excel解析的框架,但這個框架並不那麼完美。大部分使用POI都是使用他的userModel模式。userModel的好處是上手容易使用簡單,隨便拷貝個代碼跑一下,剩下就是寫業務轉換了,雖然轉換也要寫上百行代碼,相對比較好理解。然而userModel模式最大的問題是在於非常大的內存消耗,一個幾兆的文件解析要用掉上百兆的內存。現在很多應用採用這種模式,之所以還正常在跑一定是併發不大,併發上來後一定會OOM或者頻繁的full gc。

其他開源框架使用複雜

對POI有過深入瞭解的估計才知道原來POI還有SAX模式。但SAX模式相對比較複雜,excel有03和07兩種版本,兩個版本數據存儲方式截然不同,sax解析方式也各不一樣。想要了解清楚這兩種解析方式,纔去寫代碼測試,估計兩天時間是需要的。再加上即使解析完,要轉換到自己業務模型還要很多繁瑣的代碼。總體下來感覺至少需要三天,由於代碼複雜,後續維護成本巨大。

其他開源框架存在一些BUG修復不及時

由於我們的系統大多數都是大併發的情況下運行的,在大併發情況下,我們會發現poi存在一些bug,如果讓POI團隊修復估計遙遙無期了。所以我們在easyexcel對這些bug做了規避。 如下一段報錯就是在大併發情況下poi拋的一個異常。

Caused by: java.io.IOException: Could not create temporary directory '/home/admin/dio2o/.default/temp/poifiles'
        at org.apache.poi.util.DefaultTempFileCreationStrategy.createTempDirectory(DefaultTempFileCreationStrategy.java:93) ~[poi-3.15.jar:3.15]
        at org.apache.poi.util.DefaultTempFileCreationStrategy.createPOIFilesDirectory(DefaultTempFileCreationStrategy.java:82) ~[poi-3.15.jar:3.15]

報錯地方poi源碼如下

    private void createTempDirectory(File directory) throws IOException {
        if (!(directory.exists() || directory.mkdirs()) || !directory.isDirectory()) {
            throw new IOException("Could not create temporary directory '" + directory + "'");
        }
    }

仔細看代碼容易明白如果在併發情況下,如果2個線程同時判斷directory.exists()都 爲false,但執行directory.mkdirs()如果一些線程優先執行完,另外一個線程就會返回false。最終 throw new IOException("Could not create temporary directory '" + directory + "'")。針對這個問題easyexcel在寫文件時候首先創建了該臨時目錄,避免poi在併發創建時候引起不該有的報錯。

核心原理

easyExcel是阿里的一個開源項目,可以解決內存消耗大的問題,

寫有大量數據的xlsx文件時,POI爲我們提供了SXSSFWorkBook類來處理,這個類的處理機制是當內存中的數據條數達到一個極限數量的時候就flush這部分數據,再依次處理餘下的數據,這個在大多數場景能夠滿足需求。 讀有大量數據的文件時,使用WorkBook處理就不行了,因爲POI對文件是先將文件中的cell讀入內存,生成一個樹的結構(針對Excel中的每個sheet,使用TreeMap存儲sheet中的行)。如果數據量比較大,則同樣會產生java.lang.OutOfMemoryError: Java heap space錯誤。POI官方推薦使用“XSSF and SAX(event API)”方式來解決。 分析清楚POI後要解決OOM有3個關鍵。

1.文件解壓和讀取的方式是通過文件的形式,不再是poi的內存形式

img

2、避免將全部全部數據一次加載到內存

採用sax模式一行一行解析,並將一行的解析結果以觀察者的模式通知處理。

3、拋棄不重要的數據

Excel解析時候會包含樣式,字體,寬度等數據,但這些數據是我們不關心的,如果將這部分數據拋棄可以大大降低內存使用。Excel中數據如下Style佔了相當大的空間。

參考官網介紹:https://github.com/alibaba/easyexcel/blob/master/abouteasyexcel.md

參考閱讀:https://www.liangzl.com/get-article-detail-159727.html

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