Apache POI花费大量时间写入工作簿

时间:2020-04-15 04:04:19

标签: java apache-poi

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

我的意思是说,在执行此行时:

workbook.write(fileOutputStream);

几乎停止处理5000多个记录。我确认花了将近 1小时(!)来写工作簿。

如何改善性能并克服此缺点?请建议...

** 注意:其余代码是与 Apache POI 相关的常规代码,它们执行得很好,没有问题,因此我没有提及所有代码。只有我被卡在上面那行。

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

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

3 个答案:

答案 0 :(得分:2)

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

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()来清除使用的临时文件。

答案 1 :(得分:1)

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

我曾经有3000多个记录,花了10分钟才能生成输出xlsx。

使用Java探查器后,我发现 <template> <div> <input type="number" v-model="a" style="color: white" /> <input type="number" v-model="b" style="color: white" /> <p style="color: white">{{ c }}</p> </div> </template> <script> export default { data() { return { a: 1, b: 2 }; }, computed: { c: function() { return this.a + this.b; } } }; </script> 花费了大部分时间。

根据我的数据集,此方法的增长量为O(n ^ 2)(n是记录数),这说明了为什么它在较小的记录集(小于1K)中工作却突然花了这么长时间。

我检查了模板并输出,它确实有org.apache.poi.xssf.usermodel.XSSFSheet#getMergedRegion生成的许多合并单元格:

jx:each

所以我取消了Excel headers | A | B | C | | headers | `jx:each` cells | a | b | <- merged | a | b | ... | footers | 模板中的单元格的合并,现在不到1秒。

答案 2 :(得分:0)

我理解的另一个解决方案是,例如,在“行”上迭代并创建单元格时,请勿在循环内继续声明CellStylesheet.autoSizeColumn(colNumber),而是声明这2个在循环外部仅一次,并仅在循环内部设置值和样式,即cell.setCellStylecell.setCellValue

每次在迭代过程中声明上述2个,都会从根本上降低POI的性能。