原文: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(" "); } }