草庐IT

你OUT了吗,for双层循环可以使用stream方式替代

china_coding 2023-08-30 原文

本文已参与「新人创作礼」活动,一起开启掘金创作之路

大家好,我是桐言无忌,当前是不务正业的攻城狮,信奉“实践出真知,生活更简单”,向往自由。

糟粕代码

java8已经出了Stream流处理方式,但是实际业务开发时,大部分同学还是下意识的去写for双层循环。

一眼看穿繁华。。。这段代码写法就是典型的for双层循环,我们再细看业务逻辑是判断List<T>所有对象元素中有无重复的,若有重复对象主键,则抛出业务异常。

其实业务场景不复杂,那完全可以使用Stream流处理方式,那么大家还是使用for双层循环的原因是什么呢?是习惯,还是处于性能考虑呢?

做个试验,验证看看。。。

试验场

首先模拟个场景:班级里面的学生,学生与班级的匹配,检查学生是否真正是有班级的。

  1. 先模拟学生类和班级类

  2. 再搞10W+的学生数量和班级数量,不是怀疑是性能吗?数量小了可不行

  3. 搞一个for双层循环方式

    // for双层循环的方式
    private static void doubleForMethod(List<Student> studentList, List<NoClass> noClassList) {
        // 现在用学生与班级进行匹配,如果是班级号一致,认为这个学生是本班级的
        for (int i = 0; i < studentList.size(); i++) {
            Student student = studentList.get(i);
            for (int j = 0; j < noClassList.size(); j++) {
                NoClass noClass = noClassList.get(j);
                if (student.getClassesId().equals(noClass.getClassId())) {
                    // System.out.println("该学生:" + student.getStuId() + "是有班级的");
                }
            }
        }
    }
    复制代码
  4. 再搞一个Stream流方式

    private static void streamMethod(List<Student> studentList, List<NoClass> noClassList) {
        // 把班级列表转成map,那么班级id就是唯一的id
        Map<String, NoClass> noClassMap = noClassList.stream().collect(Collectors.toMap(t -> t.getClassId(), t -> t));
        // 现在用学生与班级进行匹配,如果是班级号一致,认为这个学生是本班级的
        studentList.stream().forEach(h -> {
            if (noClassMap.containsKey(h.getClassesId())) {
                // System.out.println("该学生:" + h.getStuId() + "是有班级的");
            }
        });
    }
    复制代码
  5. 运行比较两者耗时情况

    最终结果是:

    for双层循环方式耗时:80438ms

    Stream流方式耗时:80ms

同一个业务场景,二者处理结果相同,但耗时却是云泥之别,令人惊叹。

Stream在背后做了什么?

其实,我也不是很清楚,一起来学习吧。

如果一上来就了解最底层Stream是怎么实现的,这完全是和自己作对;你丫学《C语言程序设计》的时候,有学for(int i=0;i<n;i++)是怎么完成遍历的吗!

回答,肯定是没有呀。对一个知识的掌握,由浅入深,知其特性再探究原因,如果你非要一上来就看Stream源码,也行。我很期待哦,静静地看你表演。

Stream的分类

了解Stream原理之前,先要知道它的操作分类,因为Stream的操作分类就是实现高效迭代集合的原因之一。

官方将 Stream 中的操作分为两大类:中间操作(Intermediate operations)和终结操作 (Terminal operations)。中间操作只对操作进行了记录,即只会返回一个流,不会进行计算操作,而终结操作是实现了计算操作。

中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响,后者是指该操作只有拿到所有元素之后才能继续下去。

终结操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作,前者是指遇到某些符合条件的元素就可以得到最终结果,后者是指必须处理完所有元素才能得到最终结果。

我们通常还会将中间操作称为懒操作,也正是由这种懒操作结合终结操作、数据源构成的处理管道(Pipeline),实现了 Stream 的高效。

Stream的特点

  1. 数据流从一头获取数据源,在流水线上依次对元素进行操作,当元素通过流水线,便无法再对其进行操作,可以重新在数据源获取一个新的数据流进行操作;

  2. 对Collection进行处理,一般会使用 Iterator 遍历器的遍历方式,这是一种外部迭代;

    而对于处理Stream,只要申明处理方式,处理过程由流对象自行完成,这是一种内部迭代,对于大量数据的迭代处理中,内部迭代比外部迭代要更加高效;

Stream的性能

那是不是Stream就能完全取代for方式,性能更优呢?也未必。

根据官方效率数据显示:

  1. 多核 CPU 服务器配置环境下,对比长度 100 的 int 数组的性能

    常规的迭代 <Stream 并行迭代 <Stream 串行迭代

  2. 多核 CPU 服务器配置环境下,对比长度 1.00E+8 的 int 数组的性能;

    Stream 并行迭代 < 常规的迭代 <Stream 串行迭代

  3. 多核 CPU 服务器配置环境下,对比长度 1.00E+8 对象数组过滤分组的性能;

    Stream 并行迭代 < 常规的迭代 <Stream 串行迭代

  4. 单核 CPU 服务器配置环境下,对比长度 1.00E+8 对象数组过滤分组的性能;

    常规的迭代 <Stream 串行迭代 <Stream 并行迭代

建议多使用Stream吧

按官方性能统计来看,使用Stream未必可以使得遍历性能更优,具体的要依赖数据量,即实际应用场景。

不过,在我们平时的业务开发中,我建议还是多使用Stream方式。效率是写代码的考虑因素,但不是绝对因素,随着技术的发展,执行效率一定会随着硬件发展而快速提高;对于写代码的人来说,代码一定要以简洁为原则,损失一点效率,换来的是高可读的代码,我觉得是非常值得的。

来源:https://juejin.cn/post/7057694629474336775

有关你OUT了吗,for双层循环可以使用stream方式替代的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

随机推荐