JAVA 导出报表 大数据量 sxssfworkbook的使用

sxssfworkbook

之前报表导出使用得是XSSFWorkbook 但是导出数据量过大的时候经常出现OOM,现在发现使用sxssfworkbook 减少内存压力

官网是这样介绍的:
SXSSF (package: org.apache.poi.xssf.streaming) is an API-compatible streaming extension of XSSF to be used when very large spreadsheets have to be produced, and heap space is limited. SXSSF achieves its low memory footprint by limiting access to the rows that are within a sliding window, while XSSF gives access to all rows in the document. Older rows that are no longer in the window become inaccessible, as they are written to the disk.
翻译过来就是:
SXSSF (package: org.apache.poi.xssf.streaming)是XSSF的api兼容流扩展,用于必须生成非常大的电子表格,并且堆空间有限的情况下。 SXSSF通过限制对滑动窗口内的行的访问来实现低内存占用,而XSSF允许访问文档中的所有行。 不再在窗口中的旧行在被写入磁盘时变得不可访问。

简单来说 在创建 SXSSFWorkbook 对象时,你可以设置缓存和窗口的大小。这可以通过构造方法 SXSSFWorkbook(int rowAccessWindowSize) 来完成,其中 rowAccessWindowSize 表示在内存中保持的行数。这样,当超过这个数量时,将会把数据写入磁盘。然后分批写入文件。减少内存占用

简单的Demo:

import junit.framework.Assert;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
    public static void main(String[] args) throws Throwable {
    // 设置内存最大行数是1000行 (如果不带参数则默认是100行,也可以选择-1 就是全量输出)
        SXSSFWorkbook wb = new SXSSFWorkbook(100); 
        //创建sheet页
        Sheet sh = wb.createSheet();
        for(int rownum = 0; rownum < 1000; 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);
            }
        }
        //rownum < 900的行被刷新并且不可访问 (因为他设置了100行,所以他每一百行会从磁盘中加载到内存中,写入完成后 就会在内存中删除掉,接着会下一个一百行)
        for(int rownum = 0; rownum < 900; rownum++){
        //Assert.assertNotNull() 方法用于验证给定的对象不为空(即非 null)。如果 sh.getRow(rownum) 返回的对象为 null,将会抛出 AssertionError。
          Assert.assertNull(sh.getRow(rownum));
        }
        
        // 最后100行仍然在内存中
        for(int rownum = 900; rownum < 1000; rownum++){
            Assert.assertNotNull(sh.getRow(rownum));
        }
        //输出流
        FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
        //写入
        wb.write(out);
        out.close();
        wb.dispose();
    }

源码分析:

构造函数:
如果是有现有得模板,需要在模板加入数据,需要先创建XSSFWorkbook 然后在放入SXSSFWorkbook


public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles) {
        this._sxFromXHash = new HashMap();
        this._xFromSxHash = new HashMap();
        //默认100行
        this._randomAccessWindowSize = 100;
        //这个是用来判断是否创建临时文件的
        this._compressTmpFiles = false;
        this.setRandomAccessWindowSize(rowAccessWindowSize);
        this.setCompressTempFiles(compressTmpFiles);
        if (workbook == null) {
            this._wb = new XSSFWorkbook();
        } else {
            this._wb = workbook;

            for(int i = 0; i < this._wb.getNumberOfSheets(); ++i) {
                XSSFSheet sheet = this._wb.getSheetAt(i);
                this.createAndRegisterSXSSFSheet(sheet);
            }
        }

    }

创建临时文件: 其实在创建Sheet页得时候就创建了这个临时文件

//创建Sheet
 SheetDataWriter createSheetDataWriter() throws IOException {
  //判断 compressTmpFiles 构造函数默认是false,所以第一次默认执行 SheetDataWriter
        return (SheetDataWriter)(this._compressTmpFiles ? new GZIPSheetDataWriter() : new SheetDataWriter());
    }


//这个地方其实就是创建临时文件
public SheetDataWriter() throws IOException {
        this._out = this.createWriter(this._fd);
    }

 public Writer createWriter(File fd) throws IOException {
        return new BufferedWriter(new FileWriter(fd));
    }


//存储的路径:这样看其实是存在java.io.tmpdir的系统的环境变量下面
private void createPOIFilesDirectory() throws IOException {
        if (this.dir == null) {
            String tmpDir = System.getProperty("java.io.tmpdir");
            if (tmpDir == null) {
                throw new IOException("Systems temporary directory not defined - set the -Djava.io.tmpdir jvm property!");
            }

            this.dir = new File(tmpDir, "poifiles");
        }

        this.createTempDirectory(this.dir);
    } 

写入文件

public void write(OutputStream stream) throws IOException {
        Iterator i$ = this._xFromSxHash.values().iterator();

        while(i$.hasNext()) {
            SXSSFSheet sheet = (SXSSFSheet)i$.next();
            sheet.flushRows();
        }

        File tmplFile = File.createTempFile("poi-sxssf-template", ".xlsx");

        try {
            FileOutputStream os = new FileOutputStream(tmplFile);

            try {
                this._wb.write(os);
            } finally {
                os.close();
            }

            this.injectData(tmplFile, stream);
        } finally {
        //这个地方会删除掉临时文件
            tmplFile.delete();
        }

    }

创建行得时候 入临时文件

public Row createRow(int rownum) {
//这个是最大得行数 1048576 
        int maxrow = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
        if (rownum >= 0 && rownum <= maxrow) {
            if (rownum <= this._writer.getLastFlushedRow()) {
                throw new IllegalArgumentException("Attempting to write a row[" + rownum + "] " + "in the range [0," + this._writer.getLastFlushedRow() + "] that is already written to disk.");
            } else if (this._sh.getPhysicalNumberOfRows() > 0 && rownum <= this._sh.getLastRowNum()) {
                throw new IllegalArgumentException("Attempting to write a row[" + rownum + "] " + "in the range [0," + this._sh.getLastRowNum() + "] that is already written to disk.");
            } else {
            //获取要创建得行数
                Row previousRow = rownum > 0 ? this.getRow(rownum - 1) : null;
                int initialAllocationSize = 0;
                if (previousRow != null) {
                    initialAllocationSize = previousRow.getLastCellNum();
                }

                if (initialAllocationSize <= 0 && this._writer.getNumberOfFlushedRows() > 0) {
                    initialAllocationSize = this._writer.getNumberOfCellsOfLastFlushedRow();
                }

                if (initialAllocationSize <= 0) {
                    initialAllocationSize = 10;
                }

                SXSSFRow newRow = new SXSSFRow(this, initialAllocationSize);
                this._rows.put(new Integer(rownum), newRow);
 // 这儿进行了判断,如果当前行数大于randomAccessWindowSize ,则flushRows 刷新内存区域
                if (this._randomAccessWindowSize >= 0 && this._rows.size() > this._randomAccessWindowSize) {
                    try {
                        this.flushRows(this._randomAccessWindowSize);
                    } catch (IOException var7) {
                        throw new RuntimeException(var7);
                    }
                }

                return newRow;
            }
        } else {
            throw new IllegalArgumentException("Invalid row number (" + rownum + ") outside allowable range (0.." + maxrow + ")");
        }
    }

根据官网得描述 他这个刷新应该是一行一行得进行刷新:
一个包含100行窗口的工作表。 当行数达到101时,rownum=0的行被刷新到磁盘并从内存中删除,当rownum达到102时,rownum=1的行被刷新,依此类推。

总结

工作原理:
缓冲和分段写入: 当创建 SXSSFWorkbook 对象时,它会创建一个基于硬盘的滑动窗口(window)。数据不会一次性全部写入内存,而是被分为一系列窗口(windows),每个窗口中包含一定数量的行。这样可以限制内存占用,只有当前窗口中的数据会被加载到内存中。

滑动窗口的使用: 当超过窗口容量时,SXSSF 将当前窗口中的数据写入到临时文件中,并将窗口滑动到下一段数据。这种方式实现了数据的分段处理和写入,减少了内存压力。

Flush 和 Close: 当操作完成后,需要调用 flush() 方法来强制将数据写入到临时文件中。最后,调用 close() 方法关闭 SXSSFWorkbook 对象,释放资源并删除临时文件。

实测25W行得报表是没有问题得

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NPOI是一个流行的.NET库,用于读写Microsoft Office文件,包括Excel、Word和PowerPoint等。在NPOI中,SXSSF(Streaming Usermodel API for XSSF)是用于处理大量数据的一种特殊模式。 SXSSF是基于XSSF的一种优化技术,它可以处理大量数据而不会占用过多的内存。相比于XSSF的"Event-based"模型,SXSSF使用流方式将数据写入到内存中保存的XML文件中,从而减少了内存的使用。它适用于需要处理大数据的Excel文件的场景,可以减小内存占用,提高性能。 关于SXSSF的支持,NPOI提供了相应的DLL文件。我们可以从NPOI的官方网站或者GitHub仓库中下载最新版本的NPOI库,其中包含了SXSSF的相关DLL文件。将这些DLL文件引入到我们的项目中后,我们就可以使用SXSSF模式来处理大数据的Excel文件。 使用SXSSF的步骤大致如下: 1. 引入NPOI的DLL文件到项目中。 2. 创建一个SXSSFWorkbook对象,它会生成一个内存中保存XML文件的工作簿。 3. 创建SXSSFSheet对象,它用于在内存中保存工作表数据。 4. 使用SXSSFRow和SXSSFCell对象创建行和单元格,并设置相关数据。 5. 使用SXSSFSheet的writeTo方法将数据写入到内存中的XML文件。 6. 最后,通过SXSSFWorkbook的write方法将内存中的XML文件写入到硬盘上的Excel文件。 总而言之,NPOI的支持SXSSF的DLL文件提供了一种处理大数据Excel文件的解决方案,通过它我们可以更高效地处理大量数据,减小内存占用,提高性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值