Java中使用POI讀取大的Excel文件或者輸入流時發生out of memory異常參考解決方案


原文:https://www.cnblogs.com/cksvsaaa/p/7280261.html

 

注意:此參考解決方案只是針對xlsx格式的excel文件!

 

背景

    前一段時間遇到一種情況,服務器經常宕機,而且沒有規律性,查看GC日志發生了out of memory,是堆溢出導致的,分析了一下堆的dump文件,發現在發生OOM時創建了大量的String對象。最后對照時間點,發現宕機的時候業務人員在上傳一個excel文件,但是這個excel文件才28MB大小,感覺應該不會引起內存溢出。后來在本地啟動了服務,然后嘗試上傳這個excel文件,同時使用Java VisualVM監控GC情況,發現在上傳的時候,創建了大量的String對象,后來老年代沒有可分配空間導致了OOM。最終分析結果是,excel文件中存在幾十萬的空行數據,表面上看,這些空行數據跟不存在數據的行是一樣的,但是POI會把這種空行數據讀入到內存中,感覺這也是一個坑。

    在網上搜了很長時間,發現國內網站上的解決方案真是沒法看,基本上答案都差不多,沒有什么有見解性的解決方法,后來在stackoverflow上找到了解決方法。算是給自己做一下備注,也想幫助一些還在坑里的人,就分享一下,只是自己的見解,有不得當的地方也請見諒。

 

常規讀取方法

    通常在讀取excel文件時(.xlsx),是使用如下代碼進行加載的:

FileInputStream fi = new FileInputStream("e:/2.xlsx");
XSSFWorkbook wk = new XSSFWorkbook(fi);

    然后再獲取對應的Sheet、Row和Cell,然后獲取excel中的內容,但是這種方式POI會把文件的所有內容都加載到內存中,讀取大的excel文件時很容易占用大量內存。

 

嘗試解決方法

    使用Excel Streaming Reader,這個第三方工具會把一部分的行(可以設置)緩存到內存中,在迭代時不斷加載行到內存中,而不是一次性的加載所有記錄到內存,這樣就可以不斷的讀取excel內容並且不影響內存的使用。

    但是這個工具也有一定的限制:只能用於讀取excel的內容,寫入操作不可用;可以使用getSheetAt()方法獲取到對應的Sheet,因為當前只是加載了有限的row在內存中,因此不能隨機訪問row,即不能使用getRow(int rowNum)方法;由於行數據已經加載到了內存,因此可以隨機的訪問Cell數據,即可以使用getCell(int cellnum)方法。使用這個工具,建議使用迭代器來進行迭代。具體內容可以參見:https://github.com/monitorjbl/excel-streaming-reader。

    在pom.xml文件中引入需要的jar包:

        <dependency>
            <groupId>com.monitorjbl</groupId>
            <artifactId>xlsx-streamer</artifactId>
            <version>1.2.0</version>
        </dependency>

    使用代碼如下:

復制代碼
    @Test
    public void testLoad() throws Exception{
        FileInputStream in = new FileInputStream("e:/2.xlsx");
        Workbook wk = StreamingReader.builder()
                .rowCacheSize(100)  //緩存到內存中的行數,默認是10
                .bufferSize(4096)  //讀取資源時,緩存到內存的字節大小,默認是1024
                .open(in);  //打開資源,必須,可以是InputStream或者是File,注意:只能打開XLSX格式的文件
        Sheet sheet = wk.getSheetAt(0);
        //遍歷所有的行
        for (Row row : sheet) {
            System.out.println("開始遍歷第" + row.getRowNum() + "行數據:");
            //遍歷所有的列
            for (Cell cell : row) {
                System.out.print(cell.getStringCellValue() + " ");
            }
            System.out.println(" ");
        }
    }
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM