关于嵌套for循环性能问题的总结

最近,工作中需要解析大文件进行数据加工处理,其中就涉及了对于大量数据的遍历操作,如下需嵌套遍历两个集合:


//从文件中获取数据,fifContent数量为:55W;sevenContent数量为:8K
List<Map<String, String>> fifContent = getFileContent( fifData );
List<Map<String, String>> sevenContent = getFileContent( sevenData );
.....
//嵌套for循环遍历,外层for:sevenContent,内层for:fifContent
for (Map<String, String> sevenMap : sevenContent) {
     String code = sevenMap.get( "code" );
    //使用并发流
     fifContent.parallelStream().forEach( new Consumer<Map<String, String>>() {
         @Override
         public void accept(Map<String, String> stringStringMap) {
             //模拟业务处理
         }
     } );
 }

结果,运行起来相当的慢,耗时大约:446919ms (大约7分多钟)
经过分析,外层循环需遍历的数量为8K,内层循环需遍历的数量为55W,嵌套起来需要遍历的量级为:44亿 。。。
如此庞大的数据量,遍历起来不慢才怪,也曾尝试使用线程池来代替并发流,但是效果也不佳,因为本质上没有解决遍历次数的问题,此处不展开。
思考。。。。
从业务处理逻辑出发,能否避免使用嵌套循环了,如果使用一个或者多个单层循环是否也能解决问题呢?
业务逻辑:
利用外层集合中的code查找内层集合中是否存在包含此code的记录,如果包含则拿出内层集合中的记录进行加工处理;
由于思维惯性和不好的编码习惯,导致第一反应就是使用嵌套循环来实现,若数据量不大还好,数据很大的话就会变得很慢。
经分析,实际上可以先只遍历外层集合取出code放到一个Set或者List集合中,然后遍历内层集合,判断内层集合字段是否被Set集合包含,包含的话就可以继续向下处理。。
优化后的代码如下:

Set<String>codes = new HashSet<>();
//遍历集合:sevenContent(8k)
for (Map<String, String> sevenMap : sevenContent) {
    codes.add( sevenMap.get("code"));
}
//再遍历fifContent(55w),这样就不需要嵌套循环,大大降低了时间复杂度
fifContent.parallelStream().forEach( new Consumer<Map<String, String>>() {
    @Override
    public void accept(Map<String, String> stringStringMap) {
        String no = stringStringMap.get( "no" );
        if(codes.contains(no)){
           //模拟业务处理。。。
        }
    }
} );

本次运行耗时:313ms
使得效率提升的在于数据量级,请看两块代码遍历数量对比:
嵌套循环:8K * 55W
多个单循环:8K + 55W
除了上述的场景,还遇到另外一个嵌套循环场景:

/**嵌套循环的方式*/
Map<String,List<String>>testMap = new HashMap<>(  );
List<Map<String, String>> testList = new ArrayList<Map<String, String>>();

//用testMap的key值 查找testList的元素,如果一致则进行业务处理
for (Map.Entry<String, List<String>> resultEntry : testMap.entrySet()) {
    String keyCode = resultEntry.getKey();
    List<String> valueIds = resultEntry.getValue();
    for (Map<String, String> subMap : testList) {
        if(keyCode.equals(subMap.get("no"))){
             //模拟业务处理。。。           
        }
     }
}

思考:如何不用嵌套循环?
选择方式:将testList转换成Map,将no字段作为map的key,这样遍历testMap集合时,进行testList2Map.get(keyCode)便可以;

//将testList 转换成 testList2Map
HashMap<String, Map<String,String>> testList2Map = 
        testList.parallelStream().collect(
             HashMap::new, 
             (m, n) -> m.put( n.get( "bondCode" ), n ), 
             HashMap::putAll );
//仅需单个for循环遍历即可完成
for (Map.Entry<String, List<String>> resultEntry : testMap.entrySet()) {
    String keyCode = resultEntry.getKey();
    List<String> valueIds = resultEntry.getValue();
    Map<String, String> valueMap = testList2Map.get( keyBondCode );
    if(valueMap != null){
        //模拟业务处理。。。    
    }
}

思考总结:
1.使用嵌套for循环之前关注一下数据量,是否可能会带来性能问题
2.for循环遍历可考虑使用stream流或parallelStream来提升性能
3.嵌套for循环是否可以从业务逻辑上避免,如将其中一层集合遍历转换为Set或Map来取值使用,而不是逐个遍历取值。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值