记录单线程读取Excel文件慢转多线程的过程

公司需求:将Excel表格中的数据导入DB,表格中数据比较特殊,可能由30行组成一个人完整的数据,也有可能20行组成完整的数据。

一开始实现方式:单线程读取Excel文件,假如文件有6000行,以1000行为分界点,读取到1000行的时候(判断第1000行的数据是否和第998行的数据为同一人,如果为同一人则1000--,一直到不是同一人为止)起一个线程解析数据。

实现结果:读取1300条左右的数据耗时75秒,慢的过分,于是开始优化之路:

优化思路:多线程读取Excel,计算需要的线程数 线程数 = 当前行数 / 1000 向上取整,给线程分派任务,代码如下

         //文件总行数
        int lastRowNum = sheet.getLastRowNum();

        //分界点行数
        int readLine = 1000;

        //线程数 = 当前行数 / 1000 向上取整
        int threadNumber = new BigDecimal(lastRowNum).divide(new BigDecimal(readLine), BigDecimal.ROUND_UP).intValue();
        ExecutorService threadPool = Executors.newFixedThreadPool(threadNumber);
        List<XueJianDTO> xueJianDTOS;
        //起线程读取Excel数据
        for (int i = 0; i < threadNumber; i++) {
            int startLine;
            int endLine;
            //设置开始页和结束页
            if (i + 1 == threadNumber) {
                startLine = i * readLine;
                endLine = lastRowNum;
            } else {
                startLine = i * readLine;
                endLine = (i + 1) * readLine;
            }
            xueJianDTOS = new LinkedList<>();
            ReadXueJianThread xueJianThread = new ReadXueJianThread(sheet, startLine, endLine, xueJianDTOS, ShiroUtils.getUserId());
            Future<List<XueJianDTO>> future = threadPool.submit(xueJianThread);
            futures.add(future);
        }
        List<XueJianDTO> resultXueJian = new LinkedList<>();
        for (Future<List<XueJianDTO>> future : futures) {
            List<XueJianDTO> jianDTOS = future.get();
            resultXueJian.addAll(jianDTOS);
        }
        //对数据进行分组
        Map<String, List<XueJianDTO>> group = resultXueJian.stream().filter(e -> !e.getIdCard().equalsIgnoreCase("身份证号")).collect(Collectors.groupingBy(XueJianDTO::getIdCard));
        ConcurrentHashMap<String, List<XueJianDTO>> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.putAll(group);

至此Excel数据读取完毕,并且已对数据进行分组,接下来是分批插入DB,整体思路和上面差不多,计算线程数,代码如下

        //多线程将数据存至mongo  30人为一个线程 正常150人
        //线程数
        int threadCount = new BigDecimal(group.size()).divide(new BigDecimal(30), BigDecimal.ROUND_UP).intValue();
        // 子线程监控
        CountDownLatch threadLatch = new CountDownLatch(threadCount);
        threadPool = Executors.newFixedThreadPool(threadCount);
        int size = group.size();
        Iterator<Map.Entry<String, List<XueJianDTO>>> iterator = group.entrySet().iterator();
        List<XueJianDTO> xueJianDTOList = new LinkedList<>();
        while (iterator.hasNext()) {
            Map.Entry<String, List<XueJianDTO>> next = iterator.next();
            willSaveAmount++;
            for (XueJianDTO xueJianDTO : next.getValue()) {
                xueJianDTOList.add(xueJianDTO);
            }
            if (willSaveAmount == 30) {
                StorageXueJianThread storageXueJianThread = new StorageXueJianThread( threadLatch, xueJianDTOList);
                Future<Integer> future = threadPool.submit(storageXueJianThread);
                futureArrayList.add(future);
                willSaveAmount = 0;
                xueJianDTOList = new LinkedList<>();
                //读取30个人数据,每次读完从原本数量当中减去30,目的是为了最后一组线程的数据
                size -= 30;
            }
            if (willSaveAmount == size) {
                StorageXueJianThread storageXueJianThread = new StorageXueJianThread(threadLatch, xueJianDTOList);
                Future<Integer> future = threadPool.submit(storageXueJianThread);
                futureArrayList.add(future);
            }
        }

        Integer successCount = 0;
        for (Future<Integer> future : futureArrayList) {
            successCount += future.get();
        }
        log.info("导入成功{}条数据", successCount);

至此优化完成,原本75秒的数据现在4-5秒即可完成。

但是读取Excel和将数据存储到DB中还是串行操作,进一步可以优化为并行操作,个人思路:设置一个队列,读取到30个人的数据的时候将该数据放到队列中通知另外线程导入至DB。由于个人能力原因没有完成,但是这个思路应该是可行的,希望看到文章的大佬可以不吝赐教,拜谢。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Delphi是一种用于编写Windows平台应用程序的开发工具,而SQL Server是一种关系型数据库管理系统。在Delphi中实现多线程读写SQL Server数据库有以下几个步骤: 1. 引入相关单元:首先,在Delphi中需要引入一些相关的单元,如ADODB、ComObj等,以便使用数据库访问组件和相关的COM对象。 2. 连接数据库:在多线程处理之前,需要先建立与SQL Server数据库的连接。可以使用ADODB组件中的TADOConnection来连接数据库,并设置相关的连接字符串。 3. 创建多个线程:在Delphi中创建多线程可以使用TThread类,可以派生出自定义的线程类,并在Execute方法中实现具体的数据库操作。每个线程负责自己的数据库读写操作。 4. 执行SQL语句:在每个线程的Execute方法中,可以使用TADOQuery或TADOCommand等组件来执行SQL语句。可以使用Prepare方法预编译SQL语句,然后使用Parameters设置参数值,最后通过Execute或Open方法执行SQL语句。 5. 处理数据:在读取数据库数据时,可以使用TADOQuery组件的Recordset属性来获取返回的记录集,然后使用相关的方法或属性获取数据,并进行相应的处理。 6. 线程同步:在多线程操作数据库时,需要注意线程同步问题。可以使用TMonitor类或TCriticalSection类来实现线程间的互斥访问,以避免操作冲突和数据不一致。 7. 关闭连接:在所有线程的操作都完成后,需要关闭与数据库的连接,释放相关资源和线程。 总之,通过在Delphi多线程中使用ADODB组件和相关组件,可以实现对SQL Server数据库的读写操作。但需要注意线程同步和资源管理,以避免出现问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值