【问题标题】:Apache POI is taking extremely large time to write into the WorkbookApache POI 花费大量时间来写入工作簿
【发布时间】:2020-07-27 23:23:06
【问题描述】:

我正在使用 Apache POI 创建记录并将其保存到 工作簿。 我有将近 5000 多条新记录要写入并保存到工作簿中。 但是在将fileOutputStream写入工作簿时,执行基本上停止并变慢了。

我的意思是,在执行这一行的时候:

workbook.write(fileOutputStream);

它几乎停止处理 5000 多条记录。我确认在工作簿中写入需要将近 1 小时 (!)。

如何提高性能并克服这个缺点?请建议...

** 注意:其余代码都是正常的Apache POI相关代码,它们执行良好,没有问题,因此我没有全部提及。只有我卡在上面那行。

我在这里找到了一个讨论: FileOutputStream (Apachhe POI) taking too long time to save

但是,它对我没有帮助。我需要保存整个文件。

【问题讨论】:

  • 您尝试过 HXSSFWorkbook 吗?它是一种流式变体,基本上只将必要的部分保留在内存中。你只需要在最后调用清理,否则会留下未使用的文件,使你的硬盘变得混乱。
  • “我怎样才能提高性能并克服这个缺点?”:给它更多的资源,主要是内存。 XSSFWokbook.write 需要将多个 XML 文件(可能还有其他文件)写入 ZIP 存档。这需要内存资源。但我使用XSSFWorkbook 成功编写了超过 5000 行。
  • 我也尝试使用 HSSFWorkbook,但仍然需要大量时间,将近 45-50 分钟来编写。 @Stefan Helmerichs。
  • 在我的情况下,文件使用 .xls 电子表格,也就是说,我只能使用 HSSFWorkbook ,而不是 XSSFWorkbook 。 @Axel Richter。
  • 那么你的可用系统资源肯定很低。我可以使用 512 MByte 的最大堆空间在 1 秒内将 HSSFSheet 从第 1 行填充到第 65,536 行。

标签: java apache-poi


【解决方案1】:

让我们举一个具体的例子来谈谈:

import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.util.GregorianCalendar;

class CreateExcel100000Rows {

 public static void main(String[] args) throws Exception {

System.out.println("whole program starts " + java.time.LocalDateTime.now());

  try (
   //Workbook workbook = new XSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx")
   //Workbook workbook = new SXSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx")
   Workbook workbook = new HSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xls")
   ) {

   int rows = 100000;
   if (workbook instanceof HSSFWorkbook) rows = 65536;

   Object[][] data = new Object[rows][4];
   data[0] = new Object[] {"Value", "Date", "Formatted value", "Formula"};
   for (int i = 1; i < rows; i++) {
    data[i] = new Object[] {1.23456789*i, new GregorianCalendar(2000, 0, i), 1.23456789*i, "ROUND(A" + (i+1) + ",2)"};
   }

   DataFormat dataFormat = workbook.createDataFormat();
   CellStyle dateStyle = workbook.createCellStyle();
   dateStyle.setDataFormat(dataFormat.getFormat("DDDD, MMMM, DD, YYYY"));
   CellStyle numberStyle = workbook.createCellStyle();
   numberStyle.setDataFormat(dataFormat.getFormat("#,##0.00 \" Coins\""));

   Sheet sheet = workbook.createSheet(); 

   sheet.setColumnWidth(0, 12*256);
   sheet.setColumnWidth(1, 35*256);
   sheet.setColumnWidth(2, 17*256);
   sheet.setColumnWidth(3, 10*256);

   for (int r = 0; r < data.length; r++) {
    Row row = sheet.createRow(r);
    for (int c = 0; c < data[0].length; c++) {
     Cell cell = row.createCell(c);
     if (r == 0) cell.setCellValue((String)data[r][c]);
     if (r > 0 && c == 0) {
      cell.setCellValue((Double)data[r][c]);
     } else if (r > 0 && c == 1) {
      cell.setCellValue((GregorianCalendar)data[r][c]);
      cell.setCellStyle(dateStyle);
     } else if (r > 0 && c == 2) {
      cell.setCellValue((Double)data[r][c]);
      cell.setCellStyle(numberStyle);
     } else if (r > 0 && c == 3) {
      cell.setCellFormula((String)data[r][c]);
     }
    }
   }

System.out.println("write starts " + java.time.LocalDateTime.now());
   workbook.write(fileout);
System.out.println("write ends " + java.time.LocalDateTime.now());

   if (workbook instanceof SXSSFWorkbook) ((SXSSFWorkbook)workbook).dispose();
  }

System.out.println("whole program ends " + java.time.LocalDateTime.now());

 }
}

此代码创建一个HSSFWorkbook,第一张表从第 1 行填充到第 65,536 行,在 A:D 列中具有不同类型的单元格值。

使用java -Xms256M -Xmx512M,即从 256 到 512 MByte 的堆空间,总共需要 2 秒。 HSSFWorkbook.write 不到一秒钟。

如果你这样做

...
  try (
   Workbook workbook = new XSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx")
   //Workbook workbook = new SXSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx")
   //Workbook workbook = new HSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xls")
   ) {
...

此代码创建一个XSSFWorkbook,第一张工作表从第 1 行填充到第 100,000 行,在 A:D 列中具有不同类型的单元格值。

使用java -Xms256M -Xmx512M,即从 256 到 512 MByte 的堆空间,总共需要 7 秒。 XSSFWorkbook.write 需要 2 秒。这可以通过提供更多可用堆空间来改善。

如果你这样做

...
  try (
   //Workbook workbook = new XSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx")
   Workbook workbook = new SXSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xlsx")
   //Workbook workbook = new HSSFWorkbook(); FileOutputStream fileout = new FileOutputStream("Excel.xls")
   ) {
...

此代码创建一个SXSSFWorkbook,第一张表从第 1 行填充到第 100,000 行,在 A:D 列中具有不同类型的单元格值。

使用java -Xms256M -Xmx512M,即从 256 到 512 MByte 的堆空间,总共需要 2 秒。 SXSSFWorkbook.write 不到一秒钟。

注意:使用SXSSFWorkbook((SXSSFWorkbook)workbook).dispose() 是删除使用过的临时文件所必需的。

【讨论】:

  • 是的,我在本地测试了您的代码,创建 Excel.xls 也需要几秒钟。我在自己的代码中可能有任何错误。我需要检查。感谢您的宝贵帮助。
  • @Prosenjit:经常出错的是创建单元格样式。单元格样式和单元格样式本身的字体和数据格式必须在工作簿级别创建,然后只能使用Cell.setCellStyle 应用于工作表上的单元格。有些人正在为每个单元格创建新的单元格样式。这是错误的,因为 Excel 对唯一单元格格式/单元格样式的计数有限制。
  • 是的@Axel Richer,我已经检查过了, CellStyle 的限制假设为 4000 ,除此之外,它会引发一些 POIException。因此,我在循环之前创建 CellStyle,然后遍历行以使用 cell.setCellStylecell.setCellValue 将这个 CellStyle 和 Cellvalue 设置为每个单元格。
  • @Axel Richter,我正在使用 SXSSFWorkbook 并将输出直接写入 HTTPResponse。直到上周,这一切都运行良好。有一天,将内容写入 HTTP 输出流开始花费大量时间。没有更新或更改,这一切都发生在一夜之间。这在所有环境中始终如一地发生。我们不知道为什么会发生这种情况。我们使用的是 3.9 版本的 poi 和 poi-ooxml。
【解决方案2】:

我理解的另一种解决方案,例如,在遍历 Row 并创建单元格时,不要在循环内继续声明 CellStylesheet.autoSizeColumn(colNumber),而在循环中只声明这两个在循环之外并仅在循环内部设置值和样式,即cell.setCellStylecell.setCellValue

每次迭代时都声明上述2,基本上从根本上降低了POI的性能。

【讨论】:

    【解决方案3】:

    如果您使用的是合并单元格,此答案可能会有所帮助。

    我曾经有 3000+ 条记录,生成输出 xlsx 花了 10 分钟。

    使用 Java 分析器后,我发现 org.apache.poi.xssf.usermodel.XSSFSheet#getMergedRegion 花了大部分时间。

    根据我的数据集,我发现这个方法在 O(n^2) 中增长(n 是记录数),这解释了为什么它适用于小记录集(小于 1K)但需要很多时间对于大型记录集。

    我检查了模板和输出,它有很多jx:each生成的合并单元格:

    Excel headers
    | A | B | C |
    |   headers |
    `jx:each` cells
    | a |   b   | <- merged 
    | a |   b   |
    ...
    |   footers |
    

    所以我取消了jx:each模板中的单元格,现在不到1秒。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-21
      • 2014-06-13
      • 1970-01-01
      • 1970-01-01
      • 2018-01-10
      • 2013-02-15
      • 1970-01-01
      • 2015-02-11
      相关资源
      最近更新 更多