超大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写入新的文件,来达到类似修改的操作。

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