以前根據官網給的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的代碼
若是有發現更優的方法,請在下方留言聯繫我,謝謝!