这几天因为创建Excel的性能问题,头疼了几天。
先说下我的需求:根据场次导出Excel,有多少个场次就有多少个sheet页。根据工具调试,发现创建WorkBook和Sheet真的很耗时,差不多要大约两秒的时间,所以想用多线程去操作。但是创建sheet的方法底层并没有做加锁处理,导致它每次创建都会去读当前的sheet总数,所以无法放到多线程中。由于这边的场次量很多,数据量也很大,和产品商量讨论之后,决定使用每个场次创建一个Excel,然后打包成zip。
为了避免开启线程太多,所以使用了线程池和计数器(countdownLatch),生成完所有的Excel在进行压缩
下面是代码:
/*** 具体的线程操作方法* @param response* @throws IOException*/public void test(HttpServletResponse response) throws IOException {int num = 20;//ThreadPoolExecutor的用法 请参见这位老哥的帖子 /blog/coach-855850ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 500, 10L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(100));CountDownLatch count = new CountDownLatch(num); //这里的num是我的场次数量 createDir("xx");//这里在你服务器上报错的地址URLEncoder.encode(filename,"utf-8");response.setHeader("Content-Disposition", "attachment; filename=" + filename + ".zip");response.setContentType("application/octet-stream; charset=utf-8");OutputStream out = response.getOutputStream();//response是HttpServletResponseZipOutputStream zos = new ZipOutputStream(out);try {for (int i = 0; i < num; i++) {String sheet = "i"+num;ExcelTask myTask = new ExcelTask(zos,count,sheet/*,其他参数*/);executor.execute(myTask);//执行任务}String filename = "测试";count.await();//等待所有Excel创建完成zos.flush();} catch (Exception e) {e.printStackTrace();} finally {executor.shutdown();//关闭线程池//注意顺序zos.close();out.close();}}/*** 任务*/class ExcelTask extends Thread{//这里是所有需要传参的参数ZipOutputStream zos;CountDownLatch count;String sheetName;/*,其他参数*///********//构造方法public ExcelTask(ZipOutputStream zos,CountDownLatch count,String sheetName/*,其他参数*/){this.zos = zos;this.count = count;this.sheetName = sheetName;}@Overridepublic void run() {try {ThreadDeal(sheetName,zos);}catch (Exception e){e.printStackTrace();}//-1count.countDown();}}public void ThreadDeal(String sheetName,ZipOutputStream zos){SXSSFWorkbook workbook = new SXSSFWorkbook();SXSSFSheet sheet = workbook.createSheet(sheetName);//这里是你去创建表头以及对应数据的方法//dealData();//多线程必须加锁addLock(sheetName,zos,workbook);}//上锁private synchronized void addLock(String sheetName, ZipOutputStream zos, SXSSFWorkbook workbook) {try {ZipEntry zipEntry = new ZipEntry(sheetName+".xlsx");zos.putNextEntry(zipEntry);ByteArrayOutputStream bos = new ByteArrayOutputStream();workbook.write(bos);bos.writeTo(zos);} catch (IOException e) {e.printStackTrace();}}//还有就是记住如果不是4.0.0版本 那么一定要创建一个零时文件夹。不然高并发下会报错。
/*** 创建目录* @param destDirName 目标目录名* @return 目录创建成功返回true,否则返回false*/public static boolean createDir(String destDirName) {File dir = new File(destDirName);if(dir.exists()) {System.out.println("创建目录" + destDirName + "失败,目标目录已存在!");return false;}if(!destDirName.endsWith(File.separator))destDirName = destDirName + File.separator;// 创建单个目录if(dir.mkdirs()) {System.out.println("创建目录" + destDirName + "成功!");return true;} else {System.out.println("创建目录" + destDirName + "成功!");return false;}}
这里说明一下,具体Excel的创建方式以及格式需要你自己去定义。ExcelTask中传参一定是你需要带到线程中操作的参数
还要就是response设置的一定要放在前面,否则数量一大会出现问题。我也没懂是怎么回事,知道的朋友们可以告诉我,让学习学习!
如果觉得《关于多线程生成Excel文件并压缩成ZIP(输入输出流形式非file创建)》对你有帮助,请点赞、收藏,并留下你的观点哦!