多线程解决导出excel性能问题
第一步:Controller 发起导出数据请求
@RequestMapping(value = "/subpolicy/hdevpayback/exportOtherExcelAll.json") public void exportOtherExcelAll(final HttpServletRequest request, final HttpServletResponse response, String statDate, String uuId) { if (!LockUtils.getLock("exportHardDevExcelAll", 180)) { try { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("其他用户正在导出,请稍后再试..."); response.getWriter().flush(); } catch (Exception e) { ExceptionUtils.throwBusinessException(getClass(), "返回消息异常", e); } return; } try { RedisUtil.setKey(uuId, "1", 120); String file = hardDevPayBackService.exportOtherHardExcelAll(statDate, Constants.EXPORT_MODE_LOCAL, request.getSession() .getServletContext().getRealPath("/")); FileUtils.exportFile(response, new File(file), "其他设备返款全量导出", "csv"); RecordLogManager.getInstance().commitOriginalLog(MyJedisCon.getRedisUser(request).getUserName(), "HDEVPAYBACK_MENU", "exportAll", "设备返款全部导出"); RedisUtil.deleteKey(uuId); } finally { LockUtils.unlock("exportHardDevExcelAll"); } }
第二步:计算总记录数,根据总记录数分配线程数和每个线程处理的记录数。
@Override public String exportOtherHardExcelAll(String statDate, int mode, String filePath) { String csvFilePath = filePath + "exportTmp/hardDevAll/"; if (StringUtils.isBlank(statDate)) { statDate = DateUtil.getYesterday(); } String[] fields = {"payState", "payDate", "payNum", "payMoney", "payMark", "isPayBack", "devNumber", "devType", "equipNum", "devModel", "agentNumber", "agentName", "schoolAccount", "schoolName", "price", "payAgentName", "payAgentNumber", "paySchoolName", "paySchoolAccount", "paySchoolNumber", "formalTime", "bindTime", "category"}; String[] head = {"返款状态", "返款时间", "返款数量", "返款金额", "说明", "可否返款", "设备编号", "设备类型", "数量", "设备型号", "代理商编号", "代理商名称", "幼儿园账号", "幼儿园名称", "价格", "返款代理商", "返款代理商编号", "返款幼儿园", "返款幼儿园账号", "返款幼儿园id", "幼儿园转正时间", "绑定时间", "幼儿园类别"}; int count = countOtherExcelAll(statDate);//计算总记录数 int[] indexs = ThreadUtils.getIndex(count, 10000, 5);//根据总记录数分配线程数和每个线程处理的记录数 CountDownLatch latch = new CountDownLatch(indexs.length - 1); for (int j = 1; j < indexs.length; j++) { taskExecutor.execute(new HardDevExportThread(latch, Constants.THREAD_TYPE_OTHER_HARD_DEV, j, csvFilePath, statDate, indexs[j - 1], indexs[j] - indexs[j - 1], fields)); } String exportFilePath = getOneCsv(latch, csvFilePath, head, indexs.length); return exportFilePath; }
附:ThreadUtils.getIndex方法,最终结果 [0,10000,20000,30000,40000,50000]
public class ThreadUtils { /** * 返回每个线程的数据下标始末,限制最大线程数 * @param size 总数 * @param minSize 单个线程最小执行数量 * @param maxTask 最大线程数 * @return */ public static int[] getIndex(int size, int minSize, int maxTask) { int listIndexCount; double sizeDb = (double) size, minSizeDb = (double) minSize, maxTaskDb = (double) maxTask; if (sizeDb / minSizeDb < maxTaskDb) { listIndexCount = Double.valueOf(Math.ceil(sizeDb / minSizeDb)).intValue(); } else { listIndexCount = maxTask; } int each = Double.valueOf(Math.floor(sizeDb / listIndexCount)).intValue(); int[] indexs = new int[listIndexCount + 1]; indexs[0] = 0; int totalCount = 0; for (int i = 1; i < listIndexCount; i++) { indexs[i] = indexs[i - 1] + each; totalCount += each; } // 最后一个线程可能多分担一点 indexs[listIndexCount] = size - totalCount + indexs[listIndexCount - 1]; return indexs; } }
第三步:每个任务处理的事情 取数和导出到excel文件(每个线程导出一个文件)
@Override public void run() { try { int each = 10000; int times = Double.valueOf(Math.floor(size / each)).intValue(); int totalCount = 0; for (int i = 0; i < times; i++) { int beforeCount = totalCount; totalCount += each; List list; if (DEV_EXPORT_THREAD.equals(threadType)) {//硬件返款和其他硬件返款 list = hardDevPayBackService.getHardExcelAllByIndex(statDate, beforeCount + startIndex, each);//取数 } else { list = hardDevPayBackService.getOtherHardExcelAllByIndex(statDate, beforeCount + startIndex, each); } ExcelExport.exportCsvLocal(filePath, threadNum.toString(), list, null, fields, i == 0); } if (totalCount < size) {// 额外冗余each条的limit,以防count有漏掉 List list; if (DEV_EXPORT_THREAD.equals(threadType)) { list = hardDevPayBackService.getHardExcelAllByIndex(statDate, totalCount + startIndex, size - totalCount + each); } else { list = hardDevPayBackService.getOtherHardExcelAllByIndex(statDate, totalCount + startIndex, size - totalCount + each); } ExcelExport.exportCsvLocal(filePath, threadNum.toString(), list, null, fields, totalCount == 0);//写入excel文件 } } catch (Exception e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } }
第四步:把所有excel文件合并到一个文件
private String getOneCsv(CountDownLatch latch, String filePath, String[] head, int fileCount) { BufferedReader reader = null; BufferedWriter writer = null; try { latch.await();//等待所有线程都完成才执行。 File file = new File(filePath + "all.csv"); if (file.exists() && !file.isDirectory()) { file.delete(); } file.createNewFile(); writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath + "all.csv", true), "GB2312")); for (int i = 0; i < head.length; i++) { writer.write("\"" + head[i] + "\""); if (i < head.length - 1) { writer.write(","); } } writer.write("\r\n"); for (int i = 1; i < fileCount; i++) { reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath + i + ".csv"), "GB2312")); char[] buffer = new char[1024]; int len = 0; while ((len = reader.read(buffer)) > 0) { writer.write(buffer, 0, len); } reader.close(); writer.flush(); } } catch (Exception e) { e.printStackTrace(); return null; } finally { try { if (reader != null) { reader.close(); } writer.close(); } catch (Exception e) { e.printStackTrace(); } } return filePath + "all.csv"; }
本文暂时没有评论,来添加一个吧(●'◡'●)