本文是“Java不生成临时文件的导出下载csv文件的功能实现(1)一文的扩展,所以模拟输入和下载文件的代码同上篇文章。
在本系列第二篇文章中,我们每次都会把一个csv文件所需要的数据全部查询出来,方便是方便了,可是数据库的压力就上去了,因为最终生成的一万条数据,可能需要从数据库中查询几十万条数据作为基础,所以,我们还需要对代码做进一步的优化。
在这里,我们使用线程池和future来进行处理。
线程池代码如下:
@Configuration
@EnableAsync
public class TaskExecutePool {
@Bean("exportExecutor")
public ThreadPoolExecutor getExportExecutor() {
// 创建队列
BlockingQueue blockingQueue = new LinkedBlockingDeque<>(120);
// 核心5 最大10 队列120 超时60s 拒绝策略报异常
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,60, TimeUnit.SECONDS,blockingQueue);
return threadPoolExecutor;
}
}
然后,当然是要定义每次查询的数量。
private static final Integer groupCapacity = 2000;
接着,按照2000进行分组,得出组数。
Integer exportTotal = dataList.size();
Integer groupNum = (exportTotal + groupCapacity - 1) / groupCapacity;//计算组数
log.info("总数" + exportTotal + ", 每组" + groupCapacity + ", 组数" + groupNum);
使用Future和线程池请求调用获取导出列表。
List futures = new ArrayList<>();
for (Integer i = 0; i < groupNum; i++) {
try {
// 多线程调用获取list
Future future = exportExecutor.submit(new CallableTask(dataList, i));
futures.add(future);
} catch (RejectedExecutionException e) {
// 系统繁忙说明队列已满
log.error("第" + i + "次请求队列已满", e);
} catch (Exception e) {
log.error("获取导出列表异常" + e);
}
}
每满5组,进行一次csv文件组装。
//待生成文件的数据列表
List exportList = new ArrayList();
//返回数据的组数,每满5组,进行一次csv文件组装
int exportCount = 0;
//生成文件编号
int exportIndex = 0;
while (true) {
//有未完成的future
if (futures != null && !futures.isEmpty()) {
for (int i = 0; i < futures.size(); i++) {
Future future = futures.get(i);
// 判断future是否执行完成
if (future.isDone()) {
//从future中获取单次2000条数据,存入exportList中
exportList.addAll((Collection) future.get());
futures.remove(i);
i--;
exportCount++;
//累计获取五次时,重置exportCount,生成对应csv文件,然后清空exportList
if (exportCount >= 5) {
exportCount = 0;
exportIndex++;
// 导出excel
byte[] bytes = this.generateCsvFile(exportList, fieldNames, fieldDescs);
bytesList.add(bytes);
exportList.clear();
}
}
}
} else {//future全部完成
//如果exportCount不为零,说明还有不足1w条的数据未生成csv文件
if (exportCount > 0) {
exportIndex++;
// 导出excel
byte[] bytes = this.generateCsvFile(exportList, fieldNames, fieldDescs);
bytesList.add(bytes);
exportList.clear();
}
break;
}
}
线程池中执行的方法如下。
private class CallableTask implements Callable {
private List dataList;
private Integer i;
public CallableTask(List dataList, Integer i) {
this.i = i;
this.dataList = dataList;
}
@Override
public List call() throws Exception {
StopWatch stopWatch = new StopWatch();
stopWatch.start("调用查询接口");
//睡眠一会
sleep(new Random().nextInt(2000));
List subList = null;
//最后一组的i和组容量的乘积大于列表总大小,会造成下面else中的subList越界
if (i * groupCapacity < dataList.size()) {
//最后不足2000的数据
if ((i + 1) * groupCapacity > dataList.size()) {
subList = dataList.subList(i * groupCapacity, dataList.size());
} else {
subList = dataList.subList(i * groupCapacity, (i + 1) * groupCapacity);
}
}
stopWatch.stop();
log.info(stopWatch.prettyPrint());
return subList;
}
}
具体有不清楚的,可以去我的github看看。地址: