注意:此參考解決方案只是針對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