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

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

 

背景github

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

    在網上搜了很長時間,發現國內網站上的解決方案真是無法看,基本上答案都差很少,沒有什麼有看法性的解決方法,後來在stackoverflow上找到了解決方法。算是給本身作一下備註,也想幫助一些還在坑裏的人,就分享一下,只是本身的看法,有不得當的地方也請見諒。緩存

 

常規讀取方法服務器

    一般在讀取excel文件時(.xlsx),是使用以下代碼進行加載的:工具

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

    而後再獲取對應的Sheet、Row和Cell,而後獲取excel中的內容,可是這種方式POI會把文件的全部內容都加載到內存中,讀取大的excel文件時很容易佔用大量內存。網站

 

嘗試解決方法ui

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

    可是這個工具也有必定的限制:只能用於讀取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(" ");
        }
    }

 

參考資料:https://stackoverflow.com/questions/11891851/how-to-load-a-large-xlsx-file-with-apache-poi

相關文章
相關標籤/搜索