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

相關文章
相關標籤/搜索