最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Java 性能优化

网站源码admin3浏览0评论

Java 性能优化

嘿,各位 Java 开发小伙伴们!今天给大家分享一个前几天在项目中遇到的问题,这是实际项目中非常典型的性能优化案例,相信会让你对 Java 程序性能优化有更深入的理解和启发哦。让我们一起深入探索这次从性能困境到成功优化的精彩旅程吧。

一、遇到的问题:Stream 处理的性能困境

在我们的一个实际项目中遇到了这样一个需求:计算并导出全部人员全年的 考勤情况汇总数据。

接到需求之后,感觉问题不大,就是查询出相关的数据之后,循环查找和计算的问题,我们最初采用的是一种比较常见且看起来简洁的方法:使用 Stream 和 forEach 循环处理全部的数据。具体代码如下:

代码语言:javascript代码运行次数:0运行复制
finalSummaryList.stream().parallel().forEach(summary -> {
    cycle.stream().forEach(c -> {
        // 3.2、根据 List<NewAttendanceDetailResult> list计算年事假,病假,旷工,情况,并赋值给AttendStaffLeaveSummary对象中的kuangGong1 到kuangGong12字段,病假,事假也是如此
        String key = summary.getEmCode() + "-"+ cycle;
        //计算采用的数据源
        boolean flag = calculateOldOrNewRecord(cycle,summary.getIspaiban());
        if (flag){
             NewAttendanceDetailResult record = listAtt.stream()
                .filter(r -> summary.getEmCode().equals(r.getEmCode()) && attdate.getMonth() ==r.getAttdate().getMonth())
                .findFirst().orElse(null);

            calculateLeaveFromNewAttendanceDetailResult(record,summary);
        } else {
             AttendanceRecord oldRecord = listOld.stream()
                .filter(r -> summary.getEmCode().equals(r.getEmployeenum())  && attdate.getMonth()==r.getAttdate().getMonth())
                .findFirst().orElse(null);

            calculateLeaveFromAttendanceRecord(oldRecord,summary);
        }
        System.out.println("summary complete," + summary.getEmCode());

    });
});

在上述代码中,使用了 Java 8 的流(Stream)和 Lambda 表达式,主要功能是处理 finalSummaryList 中的元素,并且使用并行流(parallel())进行处理,目的可能是为了提高处理速度。对于 finalSummaryList 中的每个元素 summary,会遍历 cycle 中的元素 c,并执行一系列操作。

二、性能表现:耗时到达200s

这种使用 Java 8 的函数式编程特性的方式,虽然简洁明了,具有很高的可读性。然而,理想很美好,现实却很残酷。当我们在处理大量数据时,这个程序的性能表现却不尽如人意。在实际运行中,我们发现整个数据导出过程异常缓慢,经过仔细的测试和计时,这个过程居然需要 180 秒以上的时间!这严重影响了我们系统的整体性能,用户在等待数据导出时可能会遭遇长时间的延迟,导致用户体验极差,甚至可能影响业务的正常开展。

三、问题分析:Stream为什么效率低

深入分析这个性能问题,我们发现其核心问题在于 Stream流处理的特性。在这个for循环中,每次迭代都会创建一个新的Stream流,并且对数据集进行filter操作。由于filter操作需要遍历所有的元素,其时间复杂度为。想象一下,当 listAtt 数据集包含大量元素,而finalSummaryList也有众多元素时,我们就会陷入一个恶性循环:在每次for循环迭代中,都需要对listAtt 进行多次遍历和筛选操作,这就像在一个庞大的图书馆里,每次找一本书都要重新把书架上的书一本本检查一遍,效率自然低下。这种重复的高时间复杂度操作,随着数据量的增加,性能开销会呈指数级增长,最终导致程序像蜗牛一样慢,极大地影响了系统的响应速度和用户体验。

四、优化解决:转向 Map 查找的方案

为了克服这个性能瓶颈,我们开始了一系列的优化探索。经过多次尝试和测试,最终发现将 listAtt 转换为 Map 并使用 get(key) 进行查找的方式效果非常显著。

我们首先使用 stream().collect(Collectors.toMap(...))方法将数据集转换为Map。这里,我们将NewAttendanceDetailResultemCode和attDate作为键,将NewAttendanceDetailResult对象本身作为值存储在Map中。这个转换过程会遍历listAtt一次,将元素存储到Map 中。

代码语言:javascript代码运行次数:0运行复制
Map<String,NewAttendanceDetailResult> listNewResult =listAtt.stream()
// 提取每个对象的键(由emCode和attdate的年月组成)以及对应的对象本身
.collect(Collectors.toMap(
        att -> {
            String emCode = att.getEmCode();
            Date attdate =  att.getAttdate();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
            String attdateStr = sdf.format(attdate);
            return emCode + "-" + attdateStr;
        },
        att -> att
));

然后,在 for 循环中,我们通过 get(key) 方法直接从 Map 中查找元素。当我们完成这次优化并运行程序时,效果立竿见影,数据处理时间从之前的 200 秒以上迅速缩减到了仅仅 1 到 2 秒!这个结果让我们团队感到非常兴奋。

代码语言:javascript代码运行次数:0运行复制
finalSummaryList.stream().parallel().forEach(summary -> {
    cycle.stream().forEach(c -> {
        // 3.2、根据 List<NewAttendanceDetailResult> list计算年事假,病假,旷工,情况,并赋值给AttendStaffLeaveSummary对象中的kuangGong1 到kuangGong12字段,病假,事假也是如此
        String key = summary.getEmCode() + "-"+ cycle;
        //计采用的数据源
        boolean flag = CalOldOrNewRecord(cycle,summary.getIspaiban());
        if (flag){
            NewAttendanceDetailResult record = listNewResult.get(key);
            calculateLeaveFromNewAttendanceDetailResult(record,summary);
        } else {
            AttendanceRecord oldRecord= list.get(key);
            calculateLeaveFromAttendanceRecord(oldRecord,summary);
        }
        System.out.println("summary complete," + summary.getEmCode());
    });
});

优化后的结果:

五、优化原理:深入剖析性能提升的秘密

那么,为什么会有如此显著的性能提升呢 让我们来深入剖析一下其中的原理吧。

Stream 处理的性能分析

在使用Stream处理时,每次for循环迭代都会创建一个新的Stream实例,并且在filter操作中,它需要遍历List中的元素。这个filter操作实际上是在执行一个条件判断来筛选元素,对于List中的每个元素都要进行这样的判断,这意味着时间复杂度是一个

的操作。而且,由于for循环的存在,对于List中的每个元素,都会触发这样一个的查找过程,这是一个嵌套的时间复杂度问题,整体性能会变得非常糟糕,特别是当数据量较大时,计算量会成倍增加。

Map 查找的性能优势

六、总结与启示:性能优化的思考与实践经验

通过这次性能优化的实践,我们获得了很多宝贵的经验。

  • 在处理小数据集时,使用 Stream 流处理可以使代码更加简洁和具有可读性,并且由于数据量小,性能损失并不明显,是一个不错的选择。它能够让我们以一种函数式的编程风格来表达复杂的数据处理逻辑,提高代码的可维护性和开发效率。
  • 然而,当面对大数据集,尤其是在 for 循环中需要频繁查找元素时,将列表转换为 Map 进行查找是一种更优的策略。这种方式利用了 Map 的高效查找特性,能够极大地减少查找元素的时间,显著提升程序性能,让程序运行得更加流畅,避免了因性能问题导致的长时间等待和用户体验下降。

启示

  • 在开发过程中,我们不能仅仅满足于代码的简洁性和功能性,还需要考虑性能因素。对于不同的数据处理场景,要根据数据量和操作频率等因素,灵活选择合适的数据结构和算法。
  • 性能优化往往需要我们深入理解数据结构和操作的时间复杂度,分析代码的执行过程,找到性能瓶颈所在,而不是仅仅依赖于表面上的代码简洁性。有时候,看似简单的代码可能隐藏着性能陷阱,而一些看起来稍微复杂一点的优化方案,可能会带来意想不到的性能提升。

希望大家从这个案例中获得启发,在自己的开发工作中更加注重性能优化。在面对性能问题时,不要害怕尝试不同的方法,深入分析和理解底层原理,找到最适合自己项目的解决方案。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-01-21,如有侵权请联系 cloudcommunity@tencent 删除优化java数据性能性能优化

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论