最近在工作中遇到一个问题,就是一个普通的导出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;
}
因篇幅问题不能全部显示,请点此查看更多更全内容