超大Excel文件寫出(支持50w+)(二)

超大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寫入新的文件,來達到類似修改的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章