java springmvc用线程池高效导出百万级csv数据,前端展现进度条

领导说公司的报表导出太慢还没有进度条展示,以前用的poi操作excel导出xls,从数据库查询数据,写入本地文件,然后读取本地文件下载效率低下,特别是数据量大的时候。所以我对后台cms系统导出做了优化,采用导出csv格式,这种格式下速度比xls快很多。
**csv和xls的区别**

   经常会用户报表导出格式为csv和xls格式。他们的性能相差很大,我认为csv是非常高效的格式,而xls性能虽比csv差很多,但xls可以把格式做得很完美。现在我将两种不同格式做一些整理,希望对你们有点用。
xls 文件就是Microsoft excel电子表格的文件格式。我想就不用多介绍了吧,学校里多少都学过的。
CSV是最通用的一种文件格式,它可以非常容易地被导入各种PC表格及数据库中。 此文件,一行即为数据表的一行。生成数据表字段用半角逗号隔开。

CSV是文本文件,用记事本就能打开,XLS是二进制的文件只有用EXCEL才能打

CSV(以逗号分隔)

CSV (*.csv) 文件格式只能保存活动工作表中的单元格所显示的文本和数值。工作表中所有的数据行和字符都将保存。数据列以逗号分隔,每一行数据都以回车符结束。如果单元格中包含逗号,则该单元格中的内容以双引号引起。

如果单元格显示的是公式而不是数值,该公式将转换为文本方式。所有格式、图形、对象和工作表的其他内容将全部丢失。欧元符号将转换为问号。

//工具类
import java.io.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sf.utils.core.StringUtil;
import com.sf.tuxiaoer.service.SfSQLBaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//csv导出工具类
/**

  • 功能描述 CSV下载工具类 实现Callable接口call()方法

  • @author hjli

  • @date 2019/7/13

  • @param

  • @return
    */
    public class CsvDownload implements Callable {
    private static final Logger logger = LoggerFactory.getLogger(XlsDownload.class);
    private int index;
    private int defaultNums;
    private String sql_select;
    private String[] columns;
    private SfSQLBaseService sfSQLBaseService;
    private CountDownLatch latch;
    private HttpServletRequest request;
    }

    public CsvDownload (){

    }

    /**

    • 功能描述
    • @author hjli
    • @date 2019/7/13
    • @param index,//数据查询分页下标
    • defaultNums, //默认一页的数量
    • sql_select, //sql查询语句
    • columns, //列名
    • sfSQLBaseService, //jdbctemplate
    • latch, //CountDownLatch计时器
    • request //HttpServletRequest 请求
    • @return
      */
      public CsvDownload(int index,int defaultNums,String sql_select,
      String[] columns,SfSQLBaseService sfSQLBaseService,
      CountDownLatch latch,HttpServletRequest request){
      this.index = index;
      this.defaultNums = defaultNums;
      this.sql_select = sql_select;
      this.columns = columns;
      this.sfSQLBaseService = sfSQLBaseService;
      this.latch = latch;
      this.request = request;
      }

//返回csv后缀
private String getFileName(int rowSize) {
String dateString = DateTimeUtils.getServerTime(“yyyyMMddHHmmss”);
String fileName = dateString + “_” + rowSize + “.csv”;
return fileName;
}

/**
* 功能描述 CSV下载
* @author hjli
* @date 2019/7/13
* @param request,
* response, //响应
* sfSQLBaseService, //jdbctemplate
* title,//表头名
* column, //列名
* sql_select, //sql查询语句
* sql_count //查询总数
* @return void
*/
public void csvDownLoad(HttpServletRequest request,
HttpServletResponse response,
SfSQLBaseService sfSQLBaseService,
String title, String column, String sql_select, String sql_count) throws InterruptedException {

    String start_date = "";
    String end_date = "";
    
    //记录开始时间
    start_date = DateTimeUtils.getServerTime();
    
    //用于存储数据
    StringBuffer stringBuffer = new StringBuffer(1000);

    String[] titles = StringUtil.split(title,",");
    String[] columns = StringUtil.split(column,",");

    //告诉前端下载选择类型 导出类别 1:多线程、 2单线程
    request.getSession().setAttribute("downloadType", 1);
    
    //得到数据总量
    int rowSize = getSize(sfSQLBaseService, sql_count);

    //根据数据总量分配线程
    int defaultNums = 0;
    if(rowSize <= 3000){
        defaultNums = 3000;
    }else if(rowSize > 3000 && rowSize <= 10000){
        defaultNums = 5000;
    }else if(rowSize > 10000 && rowSize <= 50000){
        defaultNums = 10000;
    }else if(rowSize > 50000 && rowSize <= 100000){
        defaultNums = 50000;
    }else{
        defaultNums = 100000;
    }


   //插入表头内容
    for(int x = 0; x< titles.length; x++){
        stringBuffer.append(titles[x]);
        if(x < titles.length - 1){
            stringBuffer.append(",");
        }
    }
    //换行
    stringBuffer.append("\r\n");

   //分成几页
    int sheetNums = (int) getSheetNums(rowSize, defaultNums);

   //数据存储起来用于前端展示进度条
    //数据总量
    request.getSession().setAttribute("total_data",rowSize);

    //数据包总量
    request.getSession().setAttribute("total_packet",sheetNums);


    //手动创建线程池  根据页数动态创建线程池
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
    ExecutorService es = new ThreadPoolExecutor(sheetNums,sheetNums,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);
    
    //动态创建计数器
    CountDownLatch latch = new CountDownLatch(sheetNums);

   //存储Future数据
    List<Future> resultList = new ArrayList<>();

    try {

        for(int i=0;i<sheetNums;i++) {
            //先睡一哈,保证查询顺序执行
            Thread.sleep(108);
            
            //这里调用call()返回string
            Future<String> future = es.submit
                    (new XlsDownload(i, defaultNums, sql_select, columns, sfSQLBaseService,latch,request));
            resultList.add(future);
        }
        //阻塞主进程,等待latch.getCount()为0时放行
        latch.await();

    }catch (Exception e){
        e.printStackTrace();
    }finally {
        es.shutdownNow();//关闭线程池

        //释放session资源
        request.getSession().setAttribute("progress_flag",false);
        request.getSession().removeAttribute("total_data");
        request.getSession().removeAttribute("total_packet");
        for(int i=0;i<sheetNums;i++){
            request.getSession().removeAttribute("thread_"+i);
            request.getSession().removeAttribute("count_"+i);
            request.getSession().removeAttribute("packet_"+i);
            request.getSession().removeAttribute("size_"+i);
        }
        request.getSession().removeAttribute("downloadType");

    }

    //读取stringBuffer内容追加到主stringBuffer
    for(int x=0;x<resultList.size();x++){
        Future future = resultList.get(x);
        try {
            stringBuffer.append(future.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    logger.info("数据绑定成功,开始下载");
    logger.info("每页共有" + defaultNums);
    logger.info("共有" + sheetNums + "页");

    OutputStream out  = null;
    response.addHeader("Content-Disposition", "attachment;filename=" + getFileName(rowSize));
    response.addHeader("Content-Type","application/octet-stream;charset=UTF-8");
    logger.info("下载结束");
    end_date = DateTimeUtils.getServerTime();
    logger.info("start:"+start_date+">>end:"+end_date);
    try {
       //页面响应
        out = response.getOutputStream();
        out.write(stringBuffer.toString().getBytes("UTF-8"));
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

@Override
public String call() throws Exception {

    String tmpSelect = sql_select + " LIMIT " + index * defaultNums + ", " + defaultNums;
    List dataList = sfSQLBaseService.queryForList(tmpSelect);
    String _status = "";
    StringBuffer sb = new StringBuffer();

    for(int i=0;i<dataList.size();i++){
        Map dataMap = (Map)dataList.get(i);

        for(int y=0;y<columns.length;y++){
            String data = "-";
            if(dataMap.get(columns[y]) != null){
                data = dataMap.get(columns[y]).toString();
            }
            sb.append(data);

            if(y < columns.length - 1){
                sb.append(",");

            }
            _status = "线程 - "+(index+1)+" 资源:"+(i+1)+" / "+dataList.size()+"<br/><br>";
            request.getSession().setAttribute("thread_"+index,_status);

        }
        sb.append("\r\n");

        request.getSession().setAttribute("size_"+index,(i+1));
        request.getSession().setAttribute("count_"+index,index);
        request.getSession().setAttribute("packet_"+index,index);

    }
    logger.info("线程:{}执行完毕,线程ID:{}",index,Thread.currentThread().getName());

    latch.countDown();//线程每次执行完毕countDown

    return sb.toString();

}

前面用session存储了很多数据,主要是用于前端页面进度条展示,这里需要创建一个进度条监听的controller

//controller
import com.sf.tuxiaoer.web.BaseController;
import net.sf.json.JSONArray;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;

/**
* 功能描述 进度条监听
* @author hjli
* @date 2019/7/13
* @param
* @return
*/
@Controller
public class ProgressBar extends BaseController {

   private DecimalFormat df = new DecimalFormat("0.00");

    /**
     * 功能描述 进度条监听
     * @author hjli
     * @date 2019/7/13
     * @param response
     * @return void
     */
    @RequestMapping("/wash/progress/progressBarStatu")
    public void progressBarStatu(HttpServletResponse response){
        Map dataMap = new HashMap();
        try {
            boolean progress_flag = (boolean)request.getSession().getAttribute("progress_flag");

            //默认不展示
            boolean showFlag = false;

            //在导数据
            if(progress_flag == true){

                //导出类别 1:多线程、 2单线程
                int downloadType =  request.getSession().getAttribute("downloadType") != null ? (int)request.getSession().getAttribute("downloadType") : 9;

                //多线程
                if(downloadType == 1){

                    int total_data =request.getSession().getAttribute("total_data") != null ? (int)request.getSession().getAttribute("total_data") : 0;

                    int total_packet = request.getSession().getAttribute("total_packet") != null ? (int)request.getSession().getAttribute("total_packet") : 0;

                    //数据总量
                    dataMap.put("total_packet",total_packet);

                    //数据包
                    dataMap.put("total_data",total_data);

                    //已经有线程在下载时
                    if(total_packet > 0){
                        int _count = 0;
                        int _size = 0;
                        StringBuffer stringBuffer = new StringBuffer();
                        for(int i=0;i<total_packet;i++){
                            int packet_ = request.getSession().getAttribute("packet_"+i) != null ? (int)request.getSession().getAttribute("packet_"+i) : -1 ;
                            if(packet_ != -1){
                                String thread = request.getSession().getAttribute("thread_"+i) != null ? (String)request.getSession().getAttribute("thread_"+i) : "" ;
                                stringBuffer.append(thread+"\r\n");
                                stringBuffer.append("\r\n");
                                showFlag = true;

                                int count = request.getSession().getAttribute("count_"+i) != null ? (int)request.getSession().getAttribute("count_"+i) : -1 ;
                                if(count != -1){
                                    _count++;
                                }

                                int size = request.getSession().getAttribute("size_"+i) != null ? (int)request.getSession().getAttribute("size_"+i) : -1 ;
                                if(size != -1){
                                    _size += size;
                                }

                            }
                        }

                        //下载百分比
                        float progress = (float)_size * 100 / total_data;
                        dataMap.put("progress",df.format(progress)+"%");
                        dataMap.put("_count",_count);
                        dataMap.put("stringBuffer",stringBuffer.toString());
                    }
                }
                //单线程
                else if(downloadType == 2){

                    //资源总量
                    int total_size = request.getSession().getAttribute("total_size") != null ? (int)request.getSession().getAttribute("total_size") : 0;

                    //实时下载量
                    int size = request.getSession().getAttribute("size") != null ? (int)request.getSession().getAttribute("size") : 0;

                    if(size > 0){

                        //展现下载状态
                        showFlag = true;

                        //实时下载进度
                        String status =  (String)request.getSession().getAttribute("status");
                        dataMap.put("status",status);
                        dataMap.put("total_size",total_size);

                        //下载百分比
                        float progress = (float)size * 100 / total_size;
                        dataMap.put("progress",df.format(progress)+"%");

                    }

                }

                //导出类别 1:多线程、 2单线程
                dataMap.put("downloadType",downloadType);
            }

            //是否展现下载状态 true展现 false不展现
            dataMap.put("showFlag",showFlag);

            //进度条状态 true显示 false隐藏
            dataMap.put("progress_flag",progress_flag);


            PrintWriter out = response.getWriter();
            out.print(JSONArray.fromObject(dataMap).toString());
            out.close();
        }catch (Exception e){e.printStackTrace();}
    }

//前端js页面展示、可写在公共jsp页面这样所有页面都能调用进度条
//CSS

 .mydiv {
		-moz-border-radius: 16px;
		-webkit-border-radius: 16px;
		border-radius: 16px;
		background-color:lavender;
		text-align: center;
		z-index:99;
		width:38%;
		height: auto;
		left:38%;/*FF IE7*/
		top: 8%;/*FF IE7*/
		margin-left:-130px!important;/*FF IE7 该值为本身宽的一半 */
		margin-top:-20px!important;/*FF IE7 该值为本身高的一半*/

		margin-top:0px;

		position:fixed!important;/*FF IE7*/
		position:absolute;/*IE6*/
	}

	.bbg {
		background-color: #878b80;
		width: 100%;
		height: 100%;
		left:0;
		top:0;/*FF IE7*/
		filter:alpha(opacity=50);/*IE*/
		opacity:0.5;/*FF*/
		z-index:1;

		position:fixed!important;/*FF IE7*/
		position:absolute;/*IE6*/
	}

 //html
  <div id="bbg"  class="bbg" style="display:none;"></div>
		<div id="bpopDiv" class="mydiv" style="display:none;">
			<center>
				<div style="width: 300px;height:auto;font-size:16px;color:#6c98ff;" id="progress_bar">数据准备中,请等待...</div>
			</center>
       </div>


//js
var interval_time = 1000;
var time = 0;
   //一直唤醒
var task =	window.setInterval(function(){
        time++;
        $.ajax({
            url:'<%=basePath %>wash/progress/progressBarStatu',
            type:'post',
            success:function(data){
                var objs = jQuery.parseJSON(data);
                if(objs[0].progress_flag == true){
                    $("#bbg").css({display:'block'});
                    $("#bpopDiv").css({display:'block'});
                    var msg = "<b>数据下载中,请稍后......<b><br><br>" +
                        "耗时:"+time+"s<br><br>" ;
                    if(objs[0].showFlag == true){

                        //导出类别 1:多线程、 2单线程
						if(objs[0].downloadType == 1){
                            msg += "总资源量:"+objs[0].total_data+"<br/><br>" +
                                "数据包:"+objs[0]._count+" / "+objs[0].total_packet+"<br/><br>" ;
                            msg += ""+objs[0].stringBuffer+"<br><br>" +
                                "<span style='color:#ff713f'>总进度:"+objs[0].progress+"</span>";
						}else if(objs[0].downloadType == 2){
                            msg += ""+objs[0].status+"<br/><br>" +
								"<span style='color:#ff713f'>下载进度:"+objs[0].progress+"</span>" ;
						}
                        $("#progress_bar").html(msg);
                    }

                }else{
                    time = 0;
                    //window.clearInterval(task);
                    $("#progress_bar").html("数据准备中,请稍等...");
                    $("#bbg").css({display:'none'});
                    $("#bpopDiv").css({display:'none'});
                }

            }
        });

    },interval_time);
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值