搜索
您的当前位置:首页正文

导出Excel数据量大时间过慢问题

来源:独旅网

导出Excel数据量大时间过慢问题

最近在工作中遇到一个问题,就是一个普通的导出Excel功能,响应时间居然需要一分多钟,导出的数据量为 10W-15W条

问题发现:

导出Excel无非就三个步骤
1: 查询数据
2: 数据处理
3: 导出数据
通过打日志的方式,发现查询和导出占用的时间很短,大量的时间都用来处理数据了。
查看代码发现,数据处理部分用的for循环处理,那就意味着需要遍历10W+遍,所以这个地方肯定是不可以这么写的

解决思路:

1: 结果集判断,如果结果集数据量较大,则将结果集进行分组
2: 分组后的N组数据 通过CountDownLatch用多线程去并行处理N组数据
3: 汇总N组数据,组装到一个List里
4: 调用导出工具类执行导出方法

代码:

导出Excel方法:

    @Log(value = "导出结算明细")
    @RequestMapping("/export_detail")
    public void exportDoctorSettleDetail(SjAdsFinanceDoctorSettleDetailQuery detailQuery, HttpServletResponse response) {
        try {
            log.info("[BIZ Normal] MSG exportDoctorSettleDetail params:{}", JsonUtil.toString(detailQuery));
            //根据筛选条件查询列表信息
            List<SjAdsFinanceDoctorSettleDetail> detailList = sjAdsFinanceDoctorSettleDetailService.findDetailListForExport(detailQuery, null);
            log.info("[BIZ Normal] MSG exportDoctorSettleDetail select end");
            if (CollectionUtils.isEmpty(detailList)) {
                log.error("[BIZ Exception] Exception_MSG Class exportDoctorSettleDetail not find doctorSettleDetail, query={}", detailQuery);
            }
            List<DrugstoreDoctorSettleDetailForListVO> allDataList = new ArrayList<>();
            //如果导出的数据量过大(目前是大于1000条),则将数据分组多线程进行处理
            if (detailList.size() > Constant.EXPORT_DATA_LIMIT) {
                final CountDownLatch latch = new CountDownLatch(Constant.GROUP_SIZE);
                // 调用方法将集合分隔为多个小集合
                List<List<SjAdsFinanceDoctorSettleDetail>> groupList = groupList(detailList, Constant.GROUP_SIZE);
                // 此处线程池最好不要每次都新new,用公用的线程池,此处省事我直接new了
                ThreadPoolExecutor service = new ThreadPoolExecutor(
                        Constant.GROUP_SIZE, Constant.GROUP_SIZE, 60, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(),
                        new ThreadPoolExecutor.DiscardOldestPolicy());
                // 循环groupList,多线程处理每一个小集合数据        
                for (List<SjAdsFinanceDoctorSettleDetail> list : groupList) {
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                        	try{
                        		// 此处处理小集合的数据,做啥都可以,做你需要处理的事情
	                            List<DrugstoreDoctorSettleDetailForListVO> doctorSettleDetailList = list.stream().map((t) -> {
	                                return exportDetailToVo(t);
	                            }).collect(Collectors.toList());
	                            //将处理好的数据追加到allDataList中
	                            allDataList.addAll(doctorSettleDetailList);
	                        }catch (RuntimeException e){
				                e.getMessage();
				            }finally {
				                //不管上面执行结果如何,当前线程调用此方法,计数减一
                            	latch.countDown();
				            }
                        }
                    };
                    // 嫌麻烦的可以改用lambda写法~
                    service.execute(runnable);
                }
                try {
                    //阻塞当前线程,直到计数器的值为0
                    //上方的多个线程全部处理完数据前,此处阻塞,知道所有线程都处理完数据
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    service.shutdown();
                }
            } else {
                List<DrugstoreDoctorSettleDetailForListVO> doctorSettleDetailList = detailList.stream().map(this::exportDetailToVo).collect(Collectors.toList());
                allDataList.addAll(doctorSettleDetailList);
            }
            // 在这就可以拿到汇总的处理结果数据,如何做最后的数据导出动作了
            String fileName = detailQuery.getReportDate() + "_" + detailQuery.getReportDate() + "_结算明细_" + DateUtil.format(new Date(), DateUtil.PATTERN_YEAR2MINUTE);
            List<String> filedNames = handleManager.get(getCurrentUser().getId(), DrugstoreDoctorSettleDetailForListVO.class, allDataList);
            filedNames = filedNames.stream().filter(str -> !"doctorAccount".equals(str)).collect(Collectors.toList());
            log.info("[BIZ Normal] MSG exportDoctorSettleDetail result:{}", JsonUtil.toString(allDataList));
            EasyExcelUtil.exportExcel(response, DrugstoreDoctorSettleDetailForListVO.class, fileName).includeColumnFiledNames(filedNames).sheet(fileName).doWrite(allDataList);
            log.info("[BIZ Normal] MSG exportDoctorSettleDetail end");
        } catch (Exception e) {
            log.error("[BIZ Exception] Exception_MSG = {}", e.toString());
            throw new ServiceError("500","导出结算明细异常");
        }
    }

List分组方法

    /**
     * List分割操作
     *
     * @param sourceList 需要分割的List
     * @return 分割的list集合
     */
    public static List<List<SjAdsFinanceDoctorSettleDetail>> groupList(List<SjAdsFinanceDoctorSettleDetail> sourceList, Integer groupNum) {
        List<List<SjAdsFinanceDoctorSettleDetail>> groupList = new ArrayList<>();
        int total = sourceList.size();
        // 计算出余数
        int remainder = total % groupNum;
        // 计算出商
        int number = total / groupNum;
        // 偏移量
        int offset = 0;
        for (int i = 0; i < groupNum; i++) {
            List<SjAdsFinanceDoctorSettleDetail> subList;
            if (remainder > 0) {
                subList = sourceList.subList(i * number + offset, (i + 1) * number + offset + 1);
                remainder--;
                offset++;
            } else {
                subList = sourceList.subList(i * number + offset, (i + 1) * number + offset);
            }
            groupList.add(subList);
        }
        return groupList;
    }

因篇幅问题不能全部显示,请点此查看更多更全内容

Top