本文主要是介绍POI高效百万级别Excel导出-1min轻松解决问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
思路:在数据量达到百万级别的情况下,如果一次性读取并写入Excel会比较慢,如果将数据写入同一个Excel的多个Sheet,并分别通过多个线程分别写Sheet,效率将会提高很多。
经测试:100w行,每行16列的数据导出大概60s到70s之间。
使用工具:
// 总记录数Integer totalRowCount =ipccDao.getSearchSize("customer.selectExportCustNum", params);// 导出EXCEL文件名称String filaName = "客户信息";// 标题String[] titles = {"客户名称", "客户名称", "联系人", "联系方式", "负责人", "分配时间", "客户意向等级", "最后跟进时间", "通话状态","跟进阶段", "通话轮次","最近外呼任务","客户来源","创建时间","客户关注点","通话时长"};// 开始导入PoiUtil.exportExcelToWebsite(StrutsUtils.getResponse(), totalRowCount, filaName, titles,new WriteExcelDataDelegated() {@Overridepublic void writeExcelData(SXSSFSheet eachSheet, Integer startRowCount, Integer endRowCount,Integer currentPage, Integer pageSize) throws Exception {int startRow = (currentPage - 1) * pageSize; // 分页开始行号params.put("limit", " LIMIT " + startRow + ", " + pageSize);//List<Map<String, Object>> exportCust = customerService.exportCust(params);List<Map<String, Object>> exportCust = ipccDao.getSearchList("customer.selectCustomer", params);if (!CollectionUtils.isEmpty(exportCust)) {for (int i = startRowCount; i <= endRowCount; i++) {SXSSFRow eachDataRow = eachSheet.createRow(i);if ((i - startRowCount) < exportCust.size()) {Map<String, Object> customer = exportCust.get(i - startRowCount);eachDataRow.createCell(0).setCellValue(customer.get("name") != null ? customer.get("name") + "" : "");eachDataRow.createCell(1).setCellValue(customer.get("customer_nbr") != null? customer.get("customer_nbr") + "": "");eachDataRow.createCell(2).setCellValue(customer.get("contacts_name") != null? customer.get("contacts_name") + "": "");eachDataRow.createCell(3).setCellValue(customer.get("contacts_nbr")!=null?customer.get("contacts_nbr")+ "":"");eachDataRow.createCell(4).setCellValue(customer.get("leader")!=null?customer.get("leader") + "":"");eachDataRow.createCell(5).setCellValue(customer.get("distribution_date")!=null?customer.get("distribution_date") + "":"");eachDataRow.createCell(6).setCellValue(customer.get("cust_type")!=null?customer.get("cust_type") + "":"");eachDataRow.createCell(7).setCellValue(customer.get("last_contact_date")!=null?customer.get("last_contact_date") + "":"");changeCallStatusToMsg(customer);eachDataRow.createCell(8).setCellValue(customer.get("status") + "");changeContactGradation(customer);eachDataRow.createCell(9).setCellValue(customer.get("contact_gradation") + "");eachDataRow.createCell(10).setCellValue(customer.get("talk_times")!=null?customer.get("talk_times") + "":"");eachDataRow.createCell(11).setCellValue(customer.get("task_name")!=null?customer.get("task_name") + "":"");eachDataRow.createCell(12).setCellValue(customer.get("finder") !=null?customer.get("finder") + "":"");eachDataRow.createCell(13).setCellValue(customer.get("create_date")!=null?customer.get("create_date") + "":"");eachDataRow.createCell(14).setCellValue(customer.get("result_label")!=null?customer.get("result_label") + "":"");eachDataRow.createCell(15).setCellValue(customer.get("tel_time")!=null?customer.get("tel_time") + "":"");}}}}});
POIUtil.java工具类
package com.admin.util;import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;import javax.servlet.http.HttpServletResponse;import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.admin.task.ExportExcelCallable;/*** @author qjwyss* @date 2018/9/18* @description POI导出工具类*/
public class PoiUtil {/*** 每个sheet存储的记录数10W*/public static Integer PER_SHEET_ROW_COUNT;/*** 每次向EXCEL写入的记录数(查询每页数据大小) 5W*/public static Integer PER_WRITE_ROW_COUNT;static{PER_SHEET_ROW_COUNT = SysConfig.getInstance().getPropertyInt("PER_SHEET_ROW_COUNT",200000);PER_WRITE_ROW_COUNT = SysConfig.getInstance().getPropertyInt("PER_WRITE_ROW_COUNT",100000);}/*** 每个sheet的写入次数 2*/public static final Integer PER_SHEET_WRITE_COUNT = PER_SHEET_ROW_COUNT / PER_WRITE_ROW_COUNT;private final static Logger logger = LoggerFactory.getLogger(PoiUtil.class);/*** 初始化EXCEL(sheet个数和标题)** @param totalRowCount 总记录数* @param titles 标题集合* @return XSSFWorkbook对象*/public static SXSSFWorkbook initExcel(Integer totalRowCount, String[] titles) {logger.info("PER_SHEET_ROW_COUNT:{},PER_WRITE_ROW_COUNT:{}",PER_SHEET_ROW_COUNT,PER_WRITE_ROW_COUNT);// 在内存当中保持 100 行 , 超过的数据放到硬盘中在内存当中保持 100 行 , 超过的数据放到硬盘中SXSSFWorkbook wb = new SXSSFWorkbook(100);Integer sheetCount = ((totalRowCount % PER_SHEET_ROW_COUNT == 0) ?(totalRowCount / PER_SHEET_ROW_COUNT) : (totalRowCount / PER_SHEET_ROW_COUNT + 1));// 根据总记录数创建sheet并分配标题for (int i = 0; i < sheetCount; i++) {SXSSFSheet sheet = wb.createSheet("sheet" + (i + 1));sheet.setRandomAccessWindowSize(-1);SXSSFRow headRow = sheet.createRow(0);for (int j = 0; j < titles.length; j++) {SXSSFCell headRowCell = headRow.createCell(j);headRowCell.setCellValue(titles[j]);}}return wb;}/*** 下载EXCEL到本地指定的文件夹** @param wb EXCEL对象SXSSFWorkbook* @param exportPath 导出路径*/public static void downLoadExcelToLocalPath(SXSSFWorkbook wb, String exportPath) {FileOutputStream fops = null;try {fops = new FileOutputStream(exportPath);wb.write(fops);} catch (Exception e) {e.printStackTrace();} finally {if (null != wb) {try {wb.dispose();} catch (Exception e) {e.printStackTrace();}}if (null != fops) {try {fops.close();} catch (Exception e) {e.printStackTrace();}}}}/*** 下载EXCEL到浏览器** @param wb EXCEL对象XSSFWorkbook* @param response* @param fileName 文件名称* @throws IOException*/public static void downLoadExcelToWebsite(SXSSFWorkbook wb, HttpServletResponse response, String fileName) throws IOException {response.setHeader("Content-disposition", "attachment; filename="+ new String((fileName + ".xlsx").getBytes("utf-8"), "ISO8859-1"));//设置下载的文件名OutputStream outputStream = null;try {outputStream = response.getOutputStream();wb.write(outputStream);} catch (Exception e) {e.printStackTrace();} finally {if (null != wb) {try {wb.dispose();} catch (Exception e) {e.printStackTrace();}}if (null != outputStream) {try {outputStream.close();} catch (Exception e) {e.printStackTrace();}}}}/*** 导出Excel到本地指定路径** @param totalRowCount 总记录数* @param titles 标题* @param exportPath 导出路径* @param writeExcelDataDelegated 向EXCEL写数据/处理格式的委托类 自行实现* @throws Exception*/public static final void exportExcelToLocalPath(Integer totalRowCount, String[] titles, String exportPath, WriteExcelDataDelegated writeExcelDataDelegated) throws Exception {logger.info("开始导出:" + formatDate(new Date(), YYYY_MM_DD_HH_MM_SS));// 初始化EXCELSXSSFWorkbook wb = PoiUtil.initExcel(totalRowCount, titles);// 调用委托类分批写数据int sheetCount = wb.getNumberOfSheets();for (int i = 0; i < sheetCount; i++) {SXSSFSheet eachSheet = wb.getSheetAt(i);for (int j = 1; j <= PER_SHEET_WRITE_COUNT; j++) {int currentPage = i * PER_SHEET_WRITE_COUNT + j;int pageSize = PER_WRITE_ROW_COUNT;int startRowCount = (j - 1) * PER_WRITE_ROW_COUNT + 1;int endRowCount = startRowCount + pageSize - 1;writeExcelDataDelegated.writeExcelData(eachSheet, startRowCount, endRowCount, currentPage, pageSize);}}// 下载EXCELPoiUtil.downLoadExcelToLocalPath(wb, exportPath);logger.info("导出完成:" + formatDate(new Date(),"yyyy-MM-dd HH:mm:ss"));}/*** 将日期转换为字符串** @param date DATE日期* @param format 转换格式* @return 字符串日期*/public static String formatDate(Date date, String format) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);return simpleDateFormat.format(date);}public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";/*** 导出Excel到浏览器** @param response* @param totalRowCount 总记录数* @param fileName 文件名称* @param titles 标题* @param writeExcelDataDelegated 向EXCEL写数据/处理格式的委托类 自行实现* @throws Exception*/public static final void exportExcelToWebsite(HttpServletResponse response, Integer totalRowCount, String fileName, String[] titles, WriteExcelDataDelegated writeExcelDataDelegated) throws Exception {logger.info("开始导出:" + formatDate(new Date(), YYYY_MM_DD_HH_MM_SS));long start = System.currentTimeMillis();// 初始化EXCELSXSSFWorkbook wb = PoiUtil.initExcel(totalRowCount, titles);// 调用委托类分批写数据int sheetCount = wb.getNumberOfSheets();ExecutorService exportSheetPool = Executors.newFixedThreadPool(sheetCount);//工头ArrayList<Future<String>> results = new ArrayList<Future<String>>();for (int i = 0; i < sheetCount; i++) {SXSSFSheet eachSheet = wb.getSheetAt(i);for (int j = 1; j <= PER_SHEET_WRITE_COUNT; j++) {int currentPage = i * PER_SHEET_WRITE_COUNT + j;int pageSize = PER_WRITE_ROW_COUNT;int startRowCount = (j - 1) * PER_WRITE_ROW_COUNT + 1;int endRowCount = startRowCount + pageSize - 1;ExportExcelCallable exportExcelCallable = new ExportExcelCallable(writeExcelDataDelegated, eachSheet, startRowCount, endRowCount, currentPage, pageSize);results.add(exportSheetPool.submit(exportExcelCallable));//submit返回一个Future,代表了即将要返回的结果}}boolean noFinishflag = true;while(noFinishflag){for (Future<String> future : results) {//首先校验是否结束,没有结束直接跳出for循环进行下一次whileif(!future.isDone()){noFinishflag = true;break;}else{noFinishflag = false;}}}logger.info("下载前:"+formatDate(new Date(), YYYY_MM_DD_HH_MM_SS));// 下载EXCELPoiUtil.downLoadExcelToWebsite(wb, response, fileName);logger.info("下载后:"+formatDate(new Date(), YYYY_MM_DD_HH_MM_SS));long end = System.currentTimeMillis();logger.info("导出完成:" + formatDate(new Date(), YYYY_MM_DD_HH_MM_SS)+"总消耗:"+(end-start));}}
package com.admin.task;import java.util.concurrent.Callable;import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import com.admin.constant.Consts;
import com.admin.util.WriteExcelDataDelegated;/*** * 导出Excel异步处理类* 根据多个sheet创建多个线程分别去写不同的sheet,平衡最大处理量和最快处理时间。** @author jeffSheng* 2019年3月18日*/
public class ExportExcelCallable implements Callable<String>{private static final Logger logger = LoggerFactory.getLogger(ExportExcelCallable.class);private WriteExcelDataDelegated writeExcelDataDelegated;private SXSSFSheet eachSheet;private Integer startRowCount;private Integer endRowCount;private Integer currentPage;private Integer pageSize;public ExportExcelCallable(WriteExcelDataDelegated writeExcelDataDelegated,SXSSFSheet eachSheet,Integer startRowCount,Integer endRowCount,Integer currentPage,Integer pageSize){this.writeExcelDataDelegated = writeExcelDataDelegated;this.eachSheet = eachSheet;this.startRowCount = startRowCount;this.endRowCount = endRowCount;this.currentPage=currentPage;this.pageSize = pageSize;}/*** eachSheet 指定SHEETstartRowCount 开始行endRowCount 结束行currentPage 分批查询开始页pageSize 分批查询数据量*/@Overridepublic String call() throws Exception {try {writeExcelDataDelegated.writeExcelData(eachSheet, startRowCount, endRowCount, currentPage, pageSize);return Consts.Success;} catch (Exception e) {e.printStackTrace();logger.error("导出异常:"+e);}return Consts.Fail;}}
重点说明下一下方法:
/*** 初始化EXCEL(sheet个数和标题)** @param totalRowCount 总记录数* @param titles 标题集合* @return XSSFWorkbook对象*/public static SXSSFWorkbook initExcel(Integer totalRowCount, String[] titles) {logger.info("PER_SHEET_ROW_COUNT:{},PER_WRITE_ROW_COUNT:{}",PER_SHEET_ROW_COUNT,PER_WRITE_ROW_COUNT);// 在内存当中保持 100 行 , 超过的数据放到硬盘中在内存当中保持 100 行 , 超过的数据放到硬盘中SXSSFWorkbook wb = new SXSSFWorkbook(100);Integer sheetCount = ((totalRowCount % PER_SHEET_ROW_COUNT == 0) ?(totalRowCount / PER_SHEET_ROW_COUNT) : (totalRowCount / PER_SHEET_ROW_COUNT + 1));// 根据总记录数创建sheet并分配标题for (int i = 0; i < sheetCount; i++) {SXSSFSheet sheet = wb.createSheet("sheet" + (i + 1));sheet.setRandomAccessWindowSize(-1);SXSSFRow headRow = sheet.createRow(0);for (int j = 0; j < titles.length; j++) {SXSSFCell headRowCell = headRow.createCell(j);headRowCell.setCellValue(titles[j]);}}return wb;}
sheet.setRandomAccessWindowSize(-1);//对sheet设置一下无限制访问
这一行如果不添加的话就会报错:
Attempting to write a row[0] in the range [0,0] that is already written to disk.
这篇关于POI高效百万级别Excel导出-1min轻松解决问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!