POI使用多线程导出多sheet页的Excel

POI使用多线程导出多sheet页的Excel

一、前言

最近公司有个业务,需要导出多个sheet页的Excel,其中包括一个汇总sheet和8个子sheet,如下图所示。因为每个子sheet数据量可能达到1W以上,随着业务拓展,以后可能sheet会更多,因此这里需要使用多线程操作sheet导出Excel。网上查了很多资料,很多博主说POI不支持多线程操作sheet,但我就是实现了。

本文主要讲述使用多线程进行多个sheet的同时操作,这是宝贵的经验,留给自己以后参详,也留给广大网友做个参考

在这里插入图片描述

二、实现方式

由于我的业务功能限制,所以需要先准备好Excel模板,具体实现方式是,先读取excel模板中的sheet,业务中有几个sheet就循环几次,每次循环都复制第一个sheet模板页,然后使用多线程插入数据,最后删除第一个sheet页,比如:我先读取我excel模板的第一个sheet,需要做8个sheet页的导出,就循环8次,每次循环将第一个sheet复制一份出来,一共复制出来8份,最后删除第一个模板页就好了。

二、主体业务

废话不多说了,上代码

// 在类的外部声明一个原子整型变量,用来作为计数器
//提交线程任务使用的lambda表达式,lambda表达式属于内部类
//所以在外部定义int sum = 0;的话,表达式内部无法对sum进行累加
AtomicInteger cylinderNum = new AtomicInteger(0);

//定义一个长度为8的固定长度线程池。
ExecutorService executorService= Executors.newFixedThreadPool(8);
//开启一个countDownLatch计数器,等到各线程执行完成后再执行main线程
CountDownLatch countDownLatch= new CountDownLatch(fillReportBo.getStationIds().size());

for (int i=0; i<8; i++) {
   List<FillDataInfoVo> fillInfoList = fillReportMapper.getFillListDataInfo(fillReportBo);

   // 记录每次循环中数据的总量,使用原子整型变量的方法进行操作,保证线程安全
   cylinderNum.addAndGet(fillInfoList.size());

   // 复制一个子sheet工作表
   //这里十分重要,复制sheet一定要在发起线程任务之外,也就是一定要在主线程去操作,不能放在子线程里
   XSSFSheet sheetData = workbook.cloneSheet(1, "sheet" + (i+1));
   
   //这里是提前定义好的表格样式
   CellStyle finalStyleHeader = styleHeader;
   CellStyle finalStyleBody = styleBody;
   Font finalFontBody = fontBody;
   //发起线程任务,处理每个sheet的数据
   executorService.submit(() -> {
     try {
       //设置表头第一行
       XSSFRow headerRowInfoOne = sheetData.getRow(0);
       headerRowInfoOne.getCell(0).setCellValue(stationInfo.getStationShort() + "明细");

       //设置表头第二行
       XSSFRow headerRowInfoTwo = sheetData.getRow(1);
       BigDecimal fillSumInfo = fillInfoList.stream()
           .map(FillDataInfoVo::getFillNum)
           .reduce(BigDecimal.ZERO, BigDecimal::add);
       headerRowInfoTwo.getCell(1).setCellValue(fillSumInfo + " KG");
       headerRowInfoTwo.getCell(1).setCellStyle(finalStyleHeader);
       headerRowInfoTwo.getCell(3).setCellValue(fillReportBo.getFillStartTime() + "~" + fillReportBo.getFillEndTime());
       headerRowInfoTwo.getCell(3).setCellStyle(finalStyleHeader);

       设置表头第三行
       XSSFRow headerRowInfoThree = sheetData.getRow(2);
       Map<String, List<FillDataInfoVo>> devMap = StreamUtils.groupByKey(fillInfoList, FillDataInfoVo::getDevNo);
       headerRowInfoThree.getCell(1).setCellValue(devMap.size());
       headerRowInfoThree.getCell(1).setCellStyle(finalStyleHeader);
       headerRowInfoThree.getCell(3).setCellValue(fillInfoList.size());
       headerRowInfoThree.getCell(3).setCellStyle(finalStyleHeader);

       //遍历数据集,将每条数据插入excel
       for (FillDataInfoVo fillInfo : fillInfoList) {
           int indexSheet = fillInfoList.indexOf(fillInfo);
           //获取数据行
           XSSFRow row = sheetData.getRow(indexSheet + 4);
           if(StringUtils.isNull(row)){
               row = sheetData.createRow(indexSheet + 4);
           }
           for (int i = 0; i < 4; i++) {
               row.createCell(i);
           }

           // 取消行的自定义高度
           CTRow ctRow = row.getCTRow();
           ctRow.setCustomHeight(false);
           setCell(row,fillInfo);
           //设置本行所有单元格样式
           getBodyStyle(row, finalStyleBody, finalFontBody);
       }
       countDownLatch.countDown();
     } catch (Exception e) {
         e.printStackTrace();
         throw new RuntimeException(e);
     }
   });
 }
//这步一定要有,主要是为了等待所有线程执行完成
countDownLatch.await();

setCell方法主要是对当前循环行里的每一列插入数据,但是因为多线程操作的,每个子线程都在抢夺CPU资源,因此这里需要加锁,否则会出现导出数据错乱的bug

public synchronized void setCell(XSSFRow row, FillDataInfoVo fillInfo){
    row.getCell(0).setCellValue(StringUtils.isEmpty(fillInfo.getFillTime()) ? "" : fillInfo.getFillTime());
    row.getCell(1).setCellValue(StringUtils.isEmpty(fillInfo.getStationName()) ? "" : fillInfo.getStationName());
    row.getCell(2).setCellValue(StringUtils.isEmpty(fillInfo.getDevNo()) ? "" : fillInfo.getDevNo());
    row.getCell(3).setCellValue(StringUtils.isNull(fillInfo.getFillNum()) ? "" : String.valueOf(fillInfo.getFillNum()));
}

就说这么多,说太多暴露公司业务,代码给了,详细注释和注意事项都在上方,各位细品,我先搬砖去了!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值