超大Excel文件寫出(支持50w+)
1 線上內存溢出問題演示
環境準備
jvm運行參數設置如下: -Xms100M -Xmx100M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d://dump.hprof
示例代碼
/**
* 超大excel寫出示例
*
* @author Leon
* @date 2020-05-06 11:23
*/
public class WriteExcelDemo
{
public static void main(String[] args) throws Exception
{
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sheet = wb.createSheet("sheet-test");
// 模擬寫出50萬條數據
for (int i = 0; i < 500000; i++)
{
XSSFRow curRow = sheet.createRow(i);
XSSFCell cell = curRow.createCell(0);
cell.setCellValue(LocalDate.now().toString());
}
FileOutputStream fos = new FileOutputStream("d:\\test111.xlsx");
wb.write(fos);
fos.close();
}
}
運行結果
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to d://dump.hprof ...
Heap dump file created [163701970 bytes in 0.973 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at org.apache.xmlbeans.impl.store.Xobj.ensureParent(Xobj.java:614)
at org.apache.xmlbeans.impl.store.Xobj.getNormal(Xobj.java:661)
at org.apache.xmlbeans.impl.store.Cur.getNormal(Cur.java:2464)
at org.apache.xmlbeans.impl.store.Cur.skip(Cur.java:1269)
at org.apache.xmlbeans.impl.store.Cur.moveNode(Cur.java:1840)
at org.apache.xmlbeans.impl.store.Cur.createHelper(Cur.java:287)
at org.apache.xmlbeans.impl.store.Cur.createElement(Cur.java:231)
at org.apache.xmlbeans.impl.store.Cur.createElement(Cur.java:226)
at org.apache.xmlbeans.impl.store.Xobj.insertElement(Xobj.java:2116)
at org.apache.xmlbeans.impl.store.Xobj.add_element_user(Xobj.java:2197)
at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTRowImpl.addNewC(Unknown Source)
at org.apache.poi.xssf.usermodel.XSSFRow.createCell(XSSFRow.java:220)
at org.apache.poi.xssf.usermodel.XSSFRow.createCell(XSSFRow.java:198)
at com.concurrent.excel.WriteExcelDemo.main(WriteExcelDemo.java:35)
我們發現:發生內存溢出。
2 小內存實現超大excel數據寫出
上一小節,我們演示了OOM現象和原因,針對這種情況,POI官方也給出瞭解決方案。就是SXSSF
。
POI提供了SXSSF的方式可以流式的創建十分大的xlsx文件,SXSSF使用了window的概念,如果數據行已經超出window的範圍,那麼就無法修改其內容。
這個窗口的大小可以在構造函數中設定new SXSSFWorkbook(int windowSize)
也可以在sheet中設定SXSSFSheet#setRandomAccessWindowSize(int windowSize)
,其默認值爲SXSSFWorkbook.DEFAULT_WINDOW_SIZE(100)
。
還要注意SXSSF會創建一些臨時文件這個需要在finally中顯示地通過調用dispose方法清除,而且臨時文件也佔用一定硬盤,可以通過wb.setCompressTempFiles(true)
設置workbook的臨時文件使用壓縮來減少硬盤佔用。
示例如下。
示例代碼
此代碼可直接用於生產環境。
/**
* 超大excel寫出示例
*
* @author Leon
* @date 2020-05-06 11:23
*/
public class SxssfWriteExcelDemo
{
public static void main(String[] args) throws Exception
{
// jvm運行參數: -Xms100M -Xmx100M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d://dump.hprof
SXSSFWorkbook wb = new SXSSFWorkbook();
try
{
Sheet sheet = wb.createSheet();
// 模擬寫出100萬條數據
for (int i = 0; i < 1000000; i++)
{
Row curRow = sheet.createRow(i);
Cell cell = curRow.createCell(0);
cell.setCellValue(LocalDate.now().toString() + UUID.randomUUID().toString() + new Random().nextInt());
}
FileOutputStream fos = new FileOutputStream("d:\\test111.xlsx");
wb.write(fos);
fos.close();
}
finally
{
// 刪除臨時文件
if( wb != null)
{
wb.dispose();
}
}
System.out.println("ok");
}
}
運行結果
成功寫出100w條數據,並且沒有報任何錯誤。
3 小結
SXSSF可以解決寫大文件的問題,但是無法進行修改文件原有的內容,也不支持讀源文件。
如果需要,可以結合之前的讀大文件,然後將讀到的內容通過SXSSF寫入新的文件,來達到類似修改的操作。