微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

加速 Apache POI SUMIF

如何解决加速 Apache POI SUMIF

在 xlsx 工作簿中,有一些单元格带有一些无界 SUMIF 公式,如下所示:SUMIF(MySheetname!$B:$B,$E4,MySheetname!$I:$I)。 使用 Apache POI 5.0.0 对一个 SUMIF 函数的评估持续 100 毫秒,而对给定工作簿的评估持续几分钟。

改善执行持续时间的一种方法是将公式绑定到如下所示的内容SUMIF(MySheetname!$B1:$B100,MySheetname!$I1:$I100)。在我的情况下,这不是一个解决方案,因为我不是 xlsx 文件的作者,并且系统从未知的人那里获取了未知的 xlsx 文件(所以我不能只是告诉他们限制 SUMIF 范围)。

org.apache.poi.ss.formula.functions.Sumif 的当前实现会迭代给定(无界)范围内的所有单元格,因此每次评估都会迭代 1048576 个单元格。

这是方法 sumMatchingCells(AreaEval,I_MatchPredicate,AreaEval) 实现的一部分:

for (int r=0; r<height; r++) {
    for (int c=0; c<width; c++) {
        result += accumulate(aeRange,mp,aeSum,r,c);
    }
}

我想通过检查行或列是否实际存在于总和范围内来提高此方法性能。也许是这样的(使用不存在的方法 sheetContainsRowIndex):

for (int r = 0; r < height; r++) {
    if (aeSum.sheetContainsRowIndex(aeSum.getFirstRow() + r)) {
        for (int c = 0; c < width; c++) {
            if (aeSum.sheetContainsColumnIndex(aeSum.getFirstColumn() + c)) {
               [...]

LazyAreaEval 包含一个 SheetRangeEvaluator 并且它包含 SheetRefEvaluator 并且这些包含一个 EvaluationSheet 并且它至少知道 getLastRowNum()。不幸的是,这个属性链是私有的。

知道如何实现这一目标吗?或者任何其他想法如何提高 SUMIF 执行的性能

解决方法

修补 apache poi 公式评估需要深入了解来源并在评估过程中翻找。那不是我会做的。

但一种解决方法可能是在评估之前将公式中的所有完整列引用替换为从第 1 行到工作表最后一行的区域引用。

如果您只阅读工作簿,那么这只会影响随机存取内存,而不会影响存储的文件。当然,如果您需要保存更改后的工作簿,那么它会影响存储的文件。那么该解决方法可能无法使用。

当工作表中有多个具有完整列引用的公式时,这对过程持续时间有明显影响,至少使用 *.xlsx (XSSF) 并且每个公式的额外替换过程需要完成.

完整代码示例:

import java.io.FileInputStream;

import org.apache.poi.ss.formula.*;
import org.apache.poi.ss.formula.ptg.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.SpreadsheetVersion;

public class ExcelEvaluateFullColumnFormulas {

 private static String replaceFullColumnReferences(XSSFSheet sheet,String formula) {
  //System.out.println(formula);
     
  XSSFWorkbook workbook = sheet.getWorkbook();
  XSSFEvaluationWorkbook evaluationWorkbook = XSSFEvaluationWorkbook.create(workbook);
  
  Ptg[] ptgs = FormulaParser.parse(formula,(FormulaParsingWorkbook)evaluationWorkbook,FormulaType.CELL,sheet.getWorkbook().getSheetIndex(sheet));
   
  for (int i = 0; i < ptgs.length; i++) {
   if (ptgs[i] instanceof AreaPtgBase) { // the operand Ptg is an area reference
    AreaPtgBase ref = (AreaPtgBase) ptgs[i];
    if (ref.getFirstRow() == 0 && ref.getLastRow() == SpreadsheetVersion.EXCEL2007.getLastRowIndex()) { // only for full column area references
     int lastRowInSheet = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
     if (ref instanceof Area2DPtgBase) { // the area reference is a 2D area reference in same sheet
      lastRowInSheet = sheet.getLastRowNum(); // get last row of this sheet
     } else if (ref instanceof Area3DPxg) { // the area reference is a 3D area reference in another sheet
      Area3DPxg ref3D = (Area3DPxg)ref; 
      String sheetName = ref3D.getSheetName();
      lastRowInSheet = workbook.getSheet(sheetName).getLastRowNum(); // get last row of referenced sheet
     }      
     ref.setLastRow(lastRowInSheet);
     formula = FormulaRenderer.toFormulaString((FormulaRenderingWorkbook)evaluationWorkbook,ptgs);
    }
   }
  }
  //System.out.println(formula);
  return formula;
  
 }

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

  DataFormatter formatter = new DataFormatter();
  Workbook workbook = WorkbookFactory.create(new FileInputStream("test.xlsx"));  
  FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();

  Sheet sheet = workbook.getSheetAt(0);
  java.time.LocalDateTime startTime = java.time.LocalDateTime.now();
  for (Row row : sheet) {
   for (Cell cell : row) {
    ///*
    if (cell.getCellType() == CellType.FORMULA) {
     if (sheet instanceof XSSFSheet){ // do it for XSSF only,not necessary for HSSF.
      String formula = cell.getCellFormula();
      formula = replaceFullColumnReferences((XSSFSheet)sheet,formula);
      cell.setCellFormula(formula);
     }
    }
    //*/
    String value = formatter.formatCellValue(cell,evaluator);
    System.out.print(value + "\t");
   }
   System.out.println();
  }
  
 java.time.LocalDateTime endTime = java.time.LocalDateTime.now();
 java.time.Duration duration = java.time.Duration.between(startTime,endTime);
 System.out.println("process duration: " + duration);
 
 workbook.close();
 }
}

注释掉部分

...
    /*
    if (cell.getCellType() == CellType.FORMULA) {
     if (sheet instanceof XSSFSheet){ // do it for XSSF only,formula);
      cell.setCellFormula(formula);
     }
    }
    */
...

看看区别。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?