以前根据官网给的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的代码
若是有发现更优的方法,请在下方留言联系我,谢谢!