1. V1-实现思路
将所有数据一次性查出来,再写入一个页面(sheet)。考虑到查询效率和内存,一次性查出百万条数据不现实。
2003(.xls)文件:最大256(IV,2的8次方)列,最大65536(2的16次方)行;即横向256个单元格,竖向65536个单元格。(.xlsx)文件:最大16384(XFD,2的14次方)列,最大1048576(2的20次方)行;即横向16384个单元格,竖向1048576个单元格。
考虑内存溢出和查询效率。将数据分页条数设置65536,再导出Excel,具体看自身分页查询效率。
2. 引入依赖easyexcel
com.alibaba
easyexcel
2.2.6
3. 核心代码
@Override
public String queryBillExportV1() {
//TODO 1.查到账单总数
Integer total = payBillDao.countPayBill();
//TODO 2.根据sheetTotal条数分页查询 > 导出
int count = (total % sheetTotal == 0) ? total / sheetTotal : total / sheetTotal + 1;
// int count = 5;
String fileName = "D:\\360Downloads\\learn\\demo\\baoxian\\logs\\账单1.xlsx";
ExcelWriter excelWriter = null;
try {
// 这里指定文件
excelWriter = EasyExcel.write(fileName, PayBill.class).build();
// 实际使用时根据数据库分页的总的页数来。
for (int i = 1; i <= count; i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
Page
分页平均时间1-2分
写磁盘平均8s
40w数据 文件大小
posman显示请求时间16分钟
从导出结果来看40多万数据需要耗时16分钟,需要优化的地方,主要是查询效率,数据持久化到磁盘10s以内是可以接受的
4.优化分析
数据导出流程
从上图可以看出,可以优化的有两个方面
1.获取数据,利用线程池多线程查询数据
2.持久化数据,多线程写不同sheet
5.V2账单导出
- 定义线程池
private static final BlockingQueue BLOCKING_QUEUE = new ArrayBlockingQueue(100);
private static final ThreadPoolExecutor.CallerRunsPolicy POLICY = new ThreadPoolExecutor.CallerRunsPolicy();
private static int corePoolSize= Runtime.getRuntime().availableProcessors()<2?2:Runtime.getRuntime().availableProcessors();
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(corePoolSize, corePoolSize*4, 60, TimeUnit.SECONDS, BLOCKING_QUEUE, POLICY);
- 定义查询分页数据的callable
/**
* 定义多线程Callable
*/
class PoiPayBillCallable implements Callable> {
private Integer pageNo;
private Integer pageSize;
public PoiPayBillCallable(Integer pageNo, Integer pageSize) {
this.pageNo = pageNo;
this.pageSize = pageSize;
}
@Override
public List call() throws Exception {
logger.info("----当前线程:{},查询分页{}", Thread.currentThread().getName(), pageNo);
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
Page
- v2核心代码
/**
* TODO 1.查到账单总数
* TODO 2.根据sheetTotal条数分页查询-多线程查询合并条数 > 导出
*
* @return
*/
@Override
public String queryBillExportV2() {
String fileName = "D:\\360Downloads\\learn\\demo\\baoxian\\logs\\账单2.xlsx";
//TODO 1.查到账单总数
Integer total = payBillDao.countPayBill();
//TODO 2.根据sheetTotal条数分页查询 > 导出
int pageSize = 13107;
//根据sheet分区间
int count = (total % sheetTotal == 0) ? total / sheetTotal : total / sheetTotal + 1;
//分页,sheet和分页数要整除.多线程分小页查询避免oom,直接用sheetTotal做分页导出时间5分
int page = (sheetTotal % pageSize == 0) ? sheetTotal / pageSize : sheetTotal / pageSize + 1;
int pageall = (total % pageSize == 0) ? total / pageSize : total / pageSize + 1;
//创建Excel对象
ExcelWriter excelWriter = EasyExcel.write(fileName, PayBill.class).build();
try {
for (int i = 1; i <= count; i++) {
//并发编程--定义callable
List calls = new ArrayList<>();
for (int j = (i - 1) * page == 0 ? 1 : (i - 1) * page; j < i * page; j++) {
if (j > pageall) {
continue;
}
calls.add(new PoiPayBillCallable(j, pageSize));
}
if (i == count && i * page <= pageall) {
calls.add(new PoiPayBillCallable(pageall, pageSize));
}
List>> list = EXECUTOR.invokeAll(calls);
logger.info("V2结束导出sheet{}", i);
for (Future> future : list) {
final List payBills = future.get();
//持久化数据
WriteSheet writerSheet = EasyExcel.writerSheet(1, "账单" + 1).build();
excelWriter.write(payBills, writerSheet);
}
logger.info("V2结束导出sheet{}", i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
logger.info("关闭Excel流");
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
return fileName;
}
-
导出耗时,效率差不多提高了3倍
耗时6分