poi生成excel大数据量的合并单元格操做优化

以前根据官网给的api和example,对excel单元格的合并操做使用下方的代码(poi版本 3.12)api

public class MergedCells {
   public static void main(String[] args) throws IOException {
        HSSFWorkbook wb = new HSSFWorkbook();
        HSSFSheet sheet = wb.createSheet("new sheet");

        HSSFRow row = sheet.createRow(1);
        HSSFCell cell = row.createCell(1);
        cell.setCellValue("This is a test of merging");

        sheet.addMergedRegion(new CellRangeAddress(1, 1, 1, 2));

        // Write the output to a file
        FileOutputStream fileOut = new FileOutputStream("workbook.xls");
        wb.write(fileOut);
        fileOut.close();
        wb.close();
    }
}

当使用支持07以上的XSSFWorkbook和SXSSFWorkbook的时候一样能够这样,代码以下:测试

private static void mergeWithXSSF() throws  IOException{
       XSSFWorkbook wb = new XSSFWorkbook();
       XSSFSheet sheet = wb.createSheet("new sheet");

       XSSFRow row = sheet.createRow(1);
       XSSFCell cell = row.createCell(1);
       cell.setCellValue("This is a test of merging");
       sheet.addMergedRegion(new CellRangeAddress(1, 1, 1, 2));
        
       // Write the output to a file
       FileOutputStream fileOut = new FileOutputStream("d:/temp/workbook1.xlsx");
       wb.write(fileOut);
       fileOut.close();
       wb.close();
 }

可是当mergeCell的次数很是大(数万~数十万)时候,对cpu和内存消耗不只大大增长,并且耗时也很是大,本地测试的时候,循环次数6W次,大约须要20-30分钟;耗时核心代码以下:this

for(int i=0;i<100000;i++) {
      XSSFRow row = sheet.createRow(i);
      XSSFCell cell = row.createCell(1);
      cell.setCellValue("This is a test of merging");
      sheet.addMergedRegion(new CellRangeAddress(i, i, 1, 2));
  }

找遍百度和stackoverflow都没有找到合适的答案,可能这种应用场景很少(当时我遇到的需求是对20W条数据插入excel时候,前两列须要合并,由于前两列字符长度略长,后来以为能够经过设置前两列的列宽解决).net

我看了看官网,最新版本是3.16,我更新后发现 sheet 多了一个合并单元格的方法 addMergedRegionUnsafe,比addMergedRegion少了一些检测异常的过程excel

源码以下:code

public int addMergedRegion(CellRangeAddress region) {
        return this.addMergedRegion(region, true);
    }

    public int addMergedRegionUnsafe(CellRangeAddress region) {
        return this.addMergedRegion(region, false);
    }

    private int addMergedRegion(CellRangeAddress region, boolean validate) {
        if(region.getNumberOfCells() < 2) {
            throw new IllegalArgumentException("Merged region " + region.formatAsString() + " must contain 2 or more cells");
        } else {
            region.validate(SpreadsheetVersion.EXCEL2007);
            if(validate) {
                this.validateArrayFormulas(region);
                this.validateMergedRegions(region);
            }

            CTMergeCells ctMergeCells = this.worksheet.isSetMergeCells()?this.worksheet.getMergeCells():this.worksheet.addNewMergeCells();
            CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
            ctMergeCell.setRef(region.formatAsString());
            return ctMergeCells.sizeOfMergeCellArray();
        }
    }

使用这个方法以后,对于十万条数据合并单元格的本地测试就下降到了30多秒,感受真的是质的飞跃,很是高兴,可是这只是开始,我想到既然经过减小了一些异常检测就有如此神威,是否合并单元格的方法还能够继续缩减呢?orm

合并单元格的核心代码在这:对象

CTMergeCells ctMergeCells = this.worksheet.isSetMergeCells()?this.worksheet.getMergeCells():this.worksheet.addNewMergeCells();
CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
ctMergeCell.setRef(region.formatAsString());
return ctMergeCells.sizeOfMergeCellArray();

第一行经过方法名了解到,判断sheet是否已经有过合并单元格的经历,若是有就getMergeCells获得ctMergeCells 对象,不然就从addNewMergeCells获取对象(由于isSetMergeCells中使用了锁,还有一些复杂的操做,感受会比较耗时)blog

这个本身能够控制嘛~,设置一个本地变量 int mergeCellsCount = 0;若是合并了单元格 mergeCellsCount ++; 刚才核心的代码能够改为内存

CTMergeCells ctMergeCells = mergeCellsCount >0 ?this.worksheet.getMergeCells():this.worksheet.addNewMergeCells();
CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
ctMergeCell.setRef(region.formatAsString());
mergeCellsCount  ++
return ctMergeCells.sizeOfMergeCellArray();

咦,这个return是干吗的?读读源码发现 他每次合并单元格的时候还要返回 已经 合并的单元格 的数目(咱们上方定义的mergeCellsCount )

咱们只要合并的过程,这个计数对咱们没用,因此,本身重写了如下addMergedRegion方法

private void addMergeRegion(CellRangeAddress cra) {
		XSSFWorkbook workbook = new XSSFWorkbook();
		XSSFSheet sheet = workbook.getSheetAt(0);
		CTWorksheet ctWorksheet = sheet.getCTWorksheet();

		CTMergeCells ctMergeCells = mergeCellsCount > 0 ?ctWorksheet.getMergeCells():ctWorksheet.addNewMergeCells();
		CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();

		ctMergeCell.setRef(cra.formatAsString());
		mergeCellsCount ++;
	}

通过修改,再次测试,50W条数据只须要不到10秒。(10W条数据大概须要2,3秒),已经不是生成报表的时间瓶颈了,到此,收工。

总结一下:主要是CellRangeAddress 是本身定义的,本身会控制合并区域的单元格的合法性,因此去掉验证合法性的代码; 去掉返回count的代码

若是有发现更优的方法,请在下方留言联系我,谢谢!

转载请注明出处:https://my.oschina.net/u/1417838/blog/edit

相关文章
相关标签/搜索