之前介绍了如何读取较大文件的excel文件,但是都无法进行文件的写入操作,在写大文件的情况下就会出现oom

错误模拟

同之前一样,设置heap大小为100m用于模拟,之后创建简单的方法来创建一个大的xlsx

    public static void main(String[] args) throws Exception {
        // 创建Excel的工作书册 Workbook,对应到一个excel文档
        XSSFWorkbook wb = new XSSFWorkbook();

        // 创建Excel的工作sheet,对应到一个excel文档的tab
        XSSFSheet sheet = wb.createSheet("sheet1");

        for (int i = 0; i < 100000; i++) {
            // 创建Excel的sheet的一行
            XSSFRow row = sheet.createRow(i);
            // 创建一个Excel的单元格
            XSSFCell cell = row.createCell(0);
            // 给Excel的单元格设置样式和赋值
            cell.setCellValue("hello world");
        }
        FileOutputStream os = new FileOutputStream("d:\\workbook.xlsx");
        wb.write(os);
        os.close();
    }

运行之后就将报出oom

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.apache.xmlbeans.impl.store.Cur$Locations.<init>(Cur.java:493)
	at org.apache.xmlbeans.impl.store.Locale.<init>(Locale.java:168)
	at org.apache.xmlbeans.impl.store.Locale.getLocale(Locale.java:242)
	at org.apache.xmlbeans.impl.store.Locale.newInstance(Locale.java:593)
	at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderBase.newInstance(SchemaTypeLoaderBase.java:198)
	at org.apache.poi.POIXMLTypeLoader.newInstance(POIXMLTypeLoader.java:84)
	at org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst$Factory.newInstance(Unknown Source)
	at org.apache.poi.xssf.usermodel.XSSFRichTextString.<init>(XSSFRichTextString.java:87)
	at org.apache.poi.xssf.usermodel.XSSFCell.setCellValue(XSSFCell.java:426)
	at blog.excel.WriteXls.main(WriteXls.java:28)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

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的临时文件使用压缩来减少硬盘占用。下面是的一个例子:

    public static void main(String[] args)  {
        SXSSFWorkbook wb =  null;
        try {
            wb=new SXSSFWorkbook();
            Sheet sh = wb.createSheet();
            for (int rownum = 0; rownum < 1000000; rownum++) {
                Row row = sh.createRow(rownum);
                for (int cellnum = 0; cellnum < 10; cellnum++) {
                    Cell cell = row.createCell(cellnum);
                    String address = new CellReference(cell).formatAsString();
                    cell.setCellValue(address);
                }
            }

            FileOutputStream out = new FileOutputStream("d://xsxxf.xlsx");
            wb.write(out);
            out.close();
        }catch (Exception ex){
            ex.printStackTrace();
        } finally {
            // 删除临时文件
            if(wb!=null){
                wb.dispose();
            }
        }
    }

通过上面的操作可以产生大型的xlsx文件。SXXSF可以使用原有的xssf,这里创建一个template.xlsx第一个单元格输入内容

    public static void main(String[] args)  {
        SXSSFWorkbook wb =  null;
        try {
			InputStream is = new FileInputStream("d://template.xlsx");
			XSSFWorkbook xwb = new XSSFWorkbook(is);
			wb=new SXSSFWorkbook(xwb);
			Sheet sh = wb.getSheet("Sheet1");
			System.out.println(sh.getRow(0));
		}catch (Exception ex){
            ex.printStackTrace();
        } finally {
            // 删除临时文件
            if(wb!=null){
                wb.dispose();
            }
        }
	}
--------------输出---------------
null

这里取出的第一行是null,无法读取原文件,但是可以追加文件

    public static void main(String[] args)  {
        SXSSFWorkbook wb =  null;
        try {
            InputStream is = new FileInputStream("d://template.xlsx");
            XSSFWorkbook xwb = new XSSFWorkbook(is);
            wb=new SXSSFWorkbook(xwb);
            Sheet sh = wb.getSheet("Sheet1");
            for (int rownum =1; rownum < 1000000; rownum++) {//这里必须从template之后的行开始写,不然会报出“Attempting to write a row[X] in the range [X,X] that is already written to disk.”
                Row row = sh.createRow(rownum);
                for (int cellnum = 0; cellnum < 10; cellnum++) {
                    Cell cell = row.createCell(cellnum);
                    String address = new CellReference(cell).formatAsString();
                    cell.setCellValue(address);
                }
            }
            FileOutputStream out = new FileOutputStream("d://xsxxf.xlsx");
            wb.write(out);
            out.close();
        }catch (Exception ex){
            ex.printStackTrace();
        } finally {
            // 删除临时文件
            if(wb!=null){
                wb.dispose();
            }
        }
    }

总结

SXSSF可以解决写大文件的问题,但是无法进行修改文件原有的内容,也不支持读源文件。如果需要,可以结合之前的读大文件,然后将读到的内容通过SXSSF写入新的文件,来达到类似修改的操作。