excel大文件下载内存性能调优

149 阅读4分钟

 解决问题:当你后端生成excel,前端调用接口,下载大文件之后,系统是否内存飙升,而且不像想象中会自动释放内存?用visualvm或者jconsole查看发现堆内存上升后,不会自动下降,必须手动gc之后才会?

本文来详细讲解如何处理该问题。

先上代码:

Workbook  workbook  =  new SXSSFWorkbook();

try {
                workbook.write(byteArrayOutputStream);

            } catch (IOException e) {
                throw new RuntimeException(e);
            }finally {
                //  清除工作簿资源
                try {
                    workbook.close();
                    ((SXSSFWorkbook) workbook).dispose(); // 清理临时文件
                    System.gc(); // 手动调用垃圾回收器,ti
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
。。。


try {
                if(byteArrayOutputStream!=null){
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

下面详细来讲解: 

序:注意!!!

在 Apache POI 库中,​​XSSFWorkbook​​​ 和 ​​SXSSFWorkbook​​​ 类都有 ​​close()​​​ 方法,但 ​​dispose()​​​ 方法是 ​​SXSSFWorkbook​​​ 特有的,用于清理临时文件。如果你在使用 ​​XSSFWorkbook​​​ 时找不到 ​​dispose()​​​ 方法,那是因为 ​​XSSFWorkbook​​ 类本身并没有这个方法。

以下是正确的使用方法:

  1. 对于 XSSFWorkbook​:
  • 使用 ​​close()​​ 方法来关闭工作簿并释放资源。
try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
    workbook.write(out);
} finally {
    workbook.close();
}

  1. 对于 SXSSFWorkbook​:
  • 使用 ​​close()​​ 方法来关闭工作簿并释放资源。
  • 使用 ​​dispose()​​ 方法来清理临时文件。
try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
    workbook.write(out);
} finally {
    workbook.close();
    ((SXSSFWorkbook) workbook).dispose(); // 清理临时文件
}

请确保在处理大文件时使用 ​​SXSSFWorkbook​​​,这样可以有效避免内存溢出的问题,并且正确使用 ​​close()​​​ 和 ​​dispose()​​ 方法来释放资源。

也就是如果你使用的是XSSFWorkbook,哪怕调用了close()方法,临时资源也不会自动关闭,这时可以的一个辅助措施是代码显示调用gc,即 

 System.gc(); // 手动调用垃圾回收器

一.理论分析

在 Apache POI 库中,​​workbook.dispose()​​​ 和 ​​workbook.close()​​ 都是用于释放资源的方法,但它们的用途和实现有所不同。

  1. workbook.close() ​:
  • ​close()​​ 方法通常用于关闭工作簿并释放与之相关的资源。
  • 对于 ​​XSSFWorkbook​​,​​close()​​ 方法会关闭工作簿并释放所有与之相关的临时文件和资源。
  • 对于 ​​SXSSFWorkbook​​,​​close()​​ 方法会关闭工作簿并释放所有与之相关的临时文件和资源,但不会删除临时文件。
try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
    workbook.write(out);
} finally {
    workbook.close();
}

  1. workbook.dispose() ​:
  • ​dispose()​​ 方法是 ​​SXSSFWorkbook​​ 特有的方法,用于清理临时文件。
  • 在 ​​SXSSFWorkbook​​ 中,​​dispose()​​ 方法会删除所有与工作簿相关的临时文件,确保不会留下任何临时文件。
  • ​dispose()​​ 方法通常在 ​​close()​​ 方法之后调用,以确保所有资源都被正确释放。

二.操作

try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
    workbook.write(out);
} finally {
    workbook.close();
    workbook.dispose(); // 清理临时文件
}

三.总结:

  • ​workbook.close()​​ 用于关闭工作簿并释放资源。
  • ​workbook.dispose()​​​ 用于清理 ​​SXSSFWorkbook​​ 的临时文件。

在实际使用中,通常会先调用 ​​close()​​​ 方法来关闭工作簿,然后调用 ​​dispose()​​ 方法来清理临时文件。这样可以确保所有资源都被正确释放,避免资源泄漏。

四.扩展阅读

XSSFWorkbook 怎样优雅使用不造成内存溢出?

​XSSFWorkbook​​​ 是 Apache POI 库中用于处理 Excel 文件(.xlsx)的类。由于 ​​.xlsx​​​ 文件是基于 XML 的,处理大文件时可能会导致内存占用过高,甚至内存溢出。为了优雅地使用 ​​XSSFWorkbook​​ 并避免内存溢出,可以采用以下几种策略:

  1. 使用 SXSSFWorkbookSXSSFWorkbookXSSFWorkbook 的一个流式版本,它通过写入临时文件来减少内存占用。这是处理大 Excel 文件的首选方法。
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.ss.usermodel.Row;

public class ExcelWriter {
    public static void main(String[] args) throws Exception {
        SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保持最近 100 行在内存中
        SXSSFSheet sheet = workbook.createSheet("Sheet1");

        for (int rowNum = 0; rowNum < 1000; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int cellNum = 0; cellNum < 10; cellNum++) {
                row.createCell(cellNum).setCellValue("Cell " + cellNum);
            }
        }

        try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
            workbook.write(out);
        } finally {
            workbook.dispose(); // 清理临时文件
        }
    }
}

  1. 分批处理数据: 如果数据是从数据库或其他数据源读取的,可以分批读取和写入数据,而不是一次性加载所有数据。
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.ss.usermodel.Row;

public class ExcelWriter {
    public static void main(String[] args) throws Exception {
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        SXSSFSheet sheet = workbook.createSheet("Sheet1");

        int batchSize = 100;
        int totalRows = 1000;

        for (int startRow = 0; startRow < totalRows; startRow += batchSize) {
            int endRow = Math.min(startRow + batchSize, totalRows);
            for (int rowNum = startRow; rowNum < endRow; rowNum++) {
                Row row = sheet.createRow(rowNum);
                for (int cellNum = 0; cellNum < 10; cellNum++) {
                    row.createCell(cellNum).setCellValue("Cell " + cellNum);
                }
            }
            // 可以在这里进行一些清理操作,比如释放不再需要的对象
        }

        try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
            workbook.write(out);
        } finally {
            workbook.dispose();
        }
    }
}

  1. 手动管理内存: 在处理大文件时,可以手动调用垃圾回收器来帮助释放内存。
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.ss.usermodel.Row;

public class ExcelWriter {
    public static void main(String[] args) throws Exception {
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        SXSSFSheet sheet = workbook.createSheet("Sheet1");

        for (int rowNum = 0; rowNum < 1000; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int cellNum = 0; cellNum < 10; cellNum++) {
                row.createCell(cellNum).setCellValue("Cell " + cellNum);
            }
            if (rowNum % 100 == 0) {
                System.gc(); // 手动调用垃圾回收器
            }
        }

        try (FileOutputStream out = new FileOutputStream("output.xlsx")) {
            workbook.write(out);
        } finally {
            workbook.dispose();
        }
    }
}

通过使用 ​​SXSSFWorkbook​​、分批处理数据和手动管理内存,可以有效地避免使用 ​​XSSFWorkbook​​ 时可能出现的内存溢出问题。