Java ApachePOI写入工作簿的时间非常长

Java ApachePOI写入工作簿的时间非常长,java,apache-poi,Java,Apache Poi,我正在使用ApachePOI创建记录并将其保存到工作簿中。 我几乎有5000多条新记录要写入并保存到工作簿中。 但是在将fileOutputStream写入工作簿时,执行基本上停止并减慢 我的意思是,在执行这一行时: workbook.write(fileOutputStream); 它几乎停止处理5000多条记录。我验证了在工作簿中写入要花费将近1小时的时间 如何提高性能并克服此缺点??请建议 **注意:其余的代码是与ApachePOI相关的普通代码,它们执行得很好,没有问题,因此我没有提到

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

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

workbook.write(fileOutputStream);
它几乎停止处理5000多条记录。我验证了在工作簿中写入要花费将近1小时的时间


如何提高性能并克服此缺点??请建议

**注意:其余的代码是与ApachePOI相关的普通代码,它们执行得很好,没有问题,因此我没有提到所有这些代码。只是我被卡在上面的线上了

我在这里发现了一个讨论:


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

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

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());

 }
}
此代码创建一个
XSSFWorkbook
,将第一个工作表从第1行填充到第100000行,在
a:D列中具有不同类型的单元格值

使用
java-Xms256M-Xmx512M
,即从256到512兆字节的堆空间,总共需要7秒钟<代码>XSSF工作簿。写入
需要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")
   ) {
...
...
  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行填充到第100000行,在
a:D列中有不同类型的单元格值

使用
java-Xms256M-Xmx512M
,即从256到512兆字节的堆空间,这总共需要2秒钟<代码>SXSSFWorkbook.write
不到一秒钟


注意:使用
sxsfWorkbook
((sxsfWorkbook)工作簿).dispose()
是清除使用过的临时文件所必需的。

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

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());

 }
}
此代码创建一个
XSSFWorkbook
,将第一个工作表从第1行填充到第100000行,在
a:D列中具有不同类型的单元格值

使用
java-Xms256M-Xmx512M
,即从256到512兆字节的堆空间,总共需要7秒钟<代码>XSSF工作簿。写入
需要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")
   ) {
...
...
  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行填充到第100000行,在
a:D列中有不同类型的单元格值

使用
java-Xms256M-Xmx512M
,即从256到512兆字节的堆空间,这总共需要2秒钟<代码>SXSSFWorkbook.write
不到一秒钟


注意:使用
sxsfWorkbook
((sxsfWorkbook)工作簿).dispose()
是清除使用过的临时文件所必需的。

我理解的另一个解决方案是,在迭代行和创建单元格时,不要继续声明
CellStyle
sheet.autoSizeColumn(colNumber)
在循环内部,而是只在循环外部声明这两个值一次,并仅在循环内部设置值和样式,即
cell.setCellStyle
cell.setCellValue


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

我还了解一个解决方案,比如,在迭代行和创建单元格时,不要在循环中继续声明
CellStyle
sheet.autoSizeColumn(colNumber)
,相反,只在循环外部声明这2项,并仅在循环内部设置值和样式,即
cell.setCellStyle
cell.setCellValue

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

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

我曾经有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 |
Excel标题
|A | B | C|
|标题|
`jx:每个`单元
|a | b |如果使用合并单元格,此答案可能会有所帮助

我曾经有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 |
Excel标题
|A | B | C|
|标题|
`jx:每个`单元

|a | b |你有练习本吗?它是一种流媒体变体,基本上只在内存中保留必要的部分。你只需要在最后调用cleanup,否则会有未使用的文件留在硬盘上。“我如何提高性能并克服这个缺点?”:给它更多的资源,主要是内存
XSSFWokbook.write
需要将多个
XML
文件(可能还有其他文件)写入
ZIP
存档。这需要内存资源。但是我使用
XSSFWorkbook
成功地写了5000多行。我也尝试过使用HSSFWorkbook,但仍然需要大量的时间,大约45-50分钟。