十四、流式编程(4)

本章概要

  • 终端操作
    • 数组
    • 循环
    • 集合
    • 组合
    • 匹配
    • 查找
    • 信息
    • 数字流信息

终端操作

以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作(Terminal Operations)总是我们在流管道中所做的最后一件事。

数组

  • toArray():将流转换成适当类型的数组。
  • toArray(generator):在特殊情况下,生成自定义类型的数组。

当我们需要得到数组类型的数据以便于后续操作时,上面的方法就很有用。假设我们需要复用流产生的随机数时,就可以这么使用。代码示例:

import java.util.*;
import java.util.stream.*;public class RandInts {private static int[] rints = new Random(47).ints(0, 1000).limit(100).toArray();public static IntStream rands() {return Arrays.stream(rints);}
}

上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 rints 中。这样一来,每次调用 rands() 的时候可以重复获取相同的整数流。

循环

  • forEach(Consumer)常见如 System.out::println 作为 Consumer 函数。
  • forEachOrdered(Consumer): 保证 forEach 按照原始流顺序操作。

第一种形式:无序操作,仅在引入并行流时才有意义。这里简单介绍下 parallel():可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。

parallel() 看似简单,实则棘手。

下例引入 parallel() 来帮助理解 forEachOrdered(Consumer) 的作用和使用场景。代码示例:

import static base.RandInts.rands;public class ForEach {static final int SZ = 14;public static void main(String[] args) {rands().limit(SZ).forEach(n -> System.out.format("%d ", n));System.out.println();rands().limit(SZ).parallel().forEach(n -> System.out.format("%d ", n));System.out.println();rands().limit(SZ).parallel().forEachOrdered(n -> System.out.format("%d ", n));}
}

输出结果:

在这里插入图片描述

为了方便测试不同大小的流,我们抽离出了 SZ 变量。然而即使 SZ 值为14也产生了有趣的结果。在第一个流中,未使用 parallel() ,因此以元素从 rands()出来的顺序输出结果。在第二个流中,引入parallel() ,即便流很小,输出的结果的顺序也和前面不一样。这是由于多处理器并行操作的原因,如果你将程序多运行几次,你会发现输出都不相同,这是多处理器并行操作的不确定性造成的结果。

在最后一个流中,同时使用了 parallel()forEachOrdered() 来强制保持原始流顺序。因此,对非并行流使用 forEachOrdered() 是没有任何影响的。

集合

  • collect(Collector):使用 Collector 收集流元素到结果集合中。
  • collect(Supplier, BiConsumer, BiConsumer):同上,第一个参数 Supplier 创建了一个新的结果集合,第二个参数 BiConsumer 将下一个元素收集到结果集合中,第三个参数 BiConsumer 用于将两个结果集合合并起来。

在这里我们只是简单介绍了几个 Collectors 的运用示例。实际上,它还有一些非常复杂的操作实现,可通过查看 java.util.stream.Collectors 的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。

假设我们现在为了保证元素有序,将元素存储在 TreeSet 中。Collectors 里面没有特定的 toTreeSet(),但是我们可以通过将集合的构造函数引用传递给 Collectors.toCollection(),从而构建任何类型的集合。下面我们来将一个文件中的单词收集到 TreeSet 集合中。代码示例:

import java.util.*;
import java.nio.file.*;
import java.util.stream.*;public class TreeSetOfWords {public static voidmain(String[] args) throws Exception {Set<String> words2 =Files.lines(Paths.get("D:\\onJava\\myTest\\base\\TreeSetOfWords.java")).flatMap(s -> Arrays.stream(s.split("\\W+"))).filter(s -> !s.matches("\\d+")) // No numbers.map(String::trim).filter(s -> s.length() > 2).limit(100).collect(Collectors.toCollection(TreeSet::new));System.out.println(words2);}
}

输出结果:

在这里插入图片描述

Files.lines() 打开 Path 并将其转换成为由行组成的流。下一行代码以一个或多个非单词字符(\\W+)为分界,对每一行进行分割,结果是产生一个数组,然后使用 Arrays.stream() 将数组转化成为流,最后flatMap()将各行形成的多个单词流,扁平映射为一个单词流。使用 matches(\\d+) 查找并移除全部是数字的字符串(注意,words2 是通过的)。然后用 String.trim() 去除单词两边的空白,filter() 过滤所有长度小于3的单词,并只获取前100个单词,最后将其保存到 TreeSet 中。

我们也可以在流中生成 Map。代码示例:

import java.util.*;
import java.util.stream.*;class Pair {public final Character c;public final Integer i;Pair(Character c, Integer i) {this.c = c;this.i = i;}public Character getC() {return c;}public Integer getI() {return i;}@Overridepublic String toString() {return "Pair(" + c + ", " + i + ")";}
}class RandomPair {Random rand = new Random(47);// An infinite iterator of random capital letters:Iterator<Character> capChars = rand.ints(65, 91).mapToObj(i -> (char) i).iterator();public Stream<Pair> stream() {return rand.ints(100, 1000).distinct().mapToObj(i -> new Pair(capChars.next(), i));}
}public class MapCollector {public static void main(String[] args) {Map<Integer, Character> map =new RandomPair().stream().limit(8).collect(Collectors.toMap(Pair::getI, Pair::getC));System.out.println(map);}
}

输出结果:

在这里插入图片描述

Pair 只是一个基础的数据对象。RandomPair 创建了随机生成的 Pair 对象流。在 Java 中,我们不能直接以某种方式组合两个流。所以我创建了一个整数流,并且使用 mapToObj() 将整数流转化成为 Pair 流。 capChars的随机大写字母迭代器创建了流,然后next()让我们可以在stream()中使用这个流。就我所知,这是将多个流组合成新的对象流的唯一方法。

在这里,我们只使用最简单形式的 Collectors.toMap(),这个方法只需要两个从流中获取键和值的函数。还有其他重载形式,其中一种当是键发生冲突时,使用一个函数来处理冲突。

大多数情况下,java.util.stream.Collectors 中预设的 Collector 就能满足我们的要求。除此之外,你还可以使用第二种形式的 collect()。 我把它留作更高级的练习,下例给出基本用法:

SpecialCollector.java

import java.util.*;
import java.util.stream.*;public class SpecialCollector {public static void main(String[] args) throws Exception {ArrayList<String> words =FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").collect(ArrayList::new,ArrayList::add,ArrayList::addAll);words.stream().filter(s -> s.equals("cheese")).forEach(System.out::println);}
}

FileToWords.java

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.stream.Stream;public class FileToWords {public static Stream<String> stream(String filePath)throws Exception {return Files.lines(Paths.get(filePath)).skip(1) // First (comment) line.flatMap(line ->Pattern.compile("\\W+").splitAsStream(line));}
}

Cheese.dat

// streams/Cheese.dat
Not much of a cheese shop really, is it?
Finest in the district, sir.
And what leads you to that conclusion?
Well, it's so clean.
It's certainly uncontaminated by cheese.

输出结果:

在这里插入图片描述

在这里, ArrayList 的方法已经做了你所需要的操作,但更有可能的是,如果你必须使用这种形式的 collect(),就要自己创建特定的定义。

组合

  • reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional
  • reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。
  • reduce(identity, BiFunction, BinaryOperator):更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 map()reduce() 来更简单的表达它。

下面来看下 reduce 的代码示例:

import java.util.*;
import java.util.stream.*;class Frobnitz {int size;Frobnitz(int sz) {size = sz;}@Overridepublic String toString() {return "Frobnitz(" + size + ")";}// Generator:static Random rand = new Random(47);static final int BOUND = 100;static Frobnitz supply() {return new Frobnitz(rand.nextInt(BOUND));}
}public class Reduce {public static void main(String[] args) {Stream.generate(Frobnitz::supply).limit(10).peek(System.out::println).reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1).ifPresent(System.out::println);}
}

输出结果:

在这里插入图片描述

Frobnitz 包含一个可生成自身的生成器 supply() ;因为 supply() 方法作为一个 Supplier<Frobnitz> 是签名兼容的,我们可以把 supply() 作为一个方法引用传递给 Stream.generate() (这种签名兼容性被称作结构一致性)。我们使用了没有“初始值”作为第一个参数的 reduce()方法,所以产生的结果是 Optional 类型。Optional.ifPresent() 方法只有在结果非空的时候才会调用 Consumer<Frobnitz>println 方法可以被调用是因为 Frobnitz 可以通过 toString() 方法转换成 String)。

Lambda 表达式中的第一个参数 fr0reduce() 中上一次调用的结果。而第二个参数 fr1 是从流传递过来的值。

reduce() 中的 Lambda 表达式使用了三元表达式来获取结果,当 fr0size 值小于 50 的时候,将 fr0 作为结果,否则将序列中的下一个元素即 fr1作为结果。当取得第一个 size 值小于 50 的 Frobnitz,只要得到这个结果就会忽略流中其他元素。这是个非常奇怪的限制, 但也确实让我们对 reduce() 有了更多的了解。

匹配

  • allMatch(Predicate) :如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。
  • anyMatch(Predicate):如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算。
  • noneMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。

我们已经在 Prime.java 中看到了 noneMatch() 的示例;allMatch()anyMatch() 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 show()。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 Matcher 接口。代码示例:

import java.util.stream.*;
import java.util.function.*;interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {
}public class Matching {static void show(Matcher match, int val) {System.out.println(match.test(IntStream.rangeClosed(1, 9).boxed().peek(n -> System.out.format("%d ", n)),n -> n < val));}public static void main(String[] args) {show(Stream::allMatch, 10);show(Stream::allMatch, 4);show(Stream::anyMatch, 2);show(Stream::anyMatch, 0);show(Stream::noneMatch, 5);show(Stream::noneMatch, 0);}
}

输出结果:

在这里插入图片描述

BiPredicate 是一个二元谓词,它接受两个参数并返回 true 或者 false。第一个参数是我们要测试的流,第二个参数是一个谓词 PredicateMatcher 可以匹配所有的 Stream::Match 方法,所以可以将每一个Stream::*Match方法引用传递到 show() 中。对match.test() 的调用会被转换成 对方法引用Stream::Match 的调用。

show() 接受一个Matcher和一个 val 参数,val 在判断测试 n < val中指定了最大值。show() 方法生成了整数1-9组成的一个流。peek()用来展示在测试短路之前测试进行到了哪一步。从输出中可以看到每次都发生了短路。

查找

  • findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty
  • findAny(:返回含有任意流元素的 Optional,如果流为空返回 Optional.empty

代码示例:

import static base.RandInts.rands;public class SelectElement {public static void main(String[] args) {System.out.println(rands().findFirst().getAsInt());System.out.println(rands().parallel().findFirst().getAsInt());System.out.println(rands().findAny().getAsInt());System.out.println(rands().parallel().findAny().getAsInt());}
}

输出结果:

在这里插入图片描述

无论流是否为并行化,findFirst() 总是会选择流中的第一个元素。对于非并行流,findAny()会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,用 parallel() 将流并行化,以展示 findAny() 不选择流的第一个元素的可能性。

如果必须选择流中最后一个元素,那就使用 reduce()。代码示例:

import java.util.*;
import java.util.stream.*;public class LastElement {public static void main(String[] args) {OptionalInt last = IntStream.range(10, 20).reduce((n1, n2) -> n2);System.out.println(last.orElse(-1));// Non-numeric object:Optional<String> lastobj =Stream.of("one", "two", "three").reduce((n1, n2) -> n2);System.out.println(lastobj.orElse("Nothing there!"));}
}

输出结果:

在这里插入图片描述

reduce() 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字 Optional 类型( numeric optional type),否则使用 Optional 类型,就像上例中的 Optional<String>

信息

  • count():流中的元素个数。
  • max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。
  • min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。

String 类型有预设的 Comparator 实现。代码示例:

public class Informational {public static voidmain(String[] args) throws Exception {System.out.println(FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").count());System.out.println(FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").min(String.CASE_INSENSITIVE_ORDER).orElse("NONE"));System.out.println(FileToWords.stream("D:\\onJava\\myTest\\base\\Cheese.dat").max(String.CASE_INSENSITIVE_ORDER).orElse("NONE"));}
}

输出结果:

在这里插入图片描述

min()max() 的返回类型为 Optional,这需要我们使用 orElse()来解包。

数字流信息

  • average() :求取流元素平均值。
  • max()min():数值流操作无需 Comparator
  • sum():对所有流元素进行求和。
  • summaryStatistics():生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。
import static base.RandInts.rands;public class NumericStreamInfo {public static void main(String[] args) {System.out.println(rands().average().getAsDouble());System.out.println(rands().max().getAsInt());System.out.println(rands().min().getAsInt());System.out.println(rands().sum());System.out.println(rands().summaryStatistics());}
}

输出结果:

在这里插入图片描述

上例操作对于 LongStreamDoubleStream 同样适用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/87356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

火山引擎DataLeap推出两款大模型应用: 对话式检索与开发 打破代码语言屏障

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 自上世50年代&#xff0c;以“计算机”作为代表性象征的信息革命开始&#xff0c;社会对于先进生产力的认知便开始逐步更迭——从信息化&#xff08;通常认为是把企…

Coupang真的好做吗?韩国Coupang入驻流程——站斧浏览器

coupang真的好做吗&#xff1f; Coupang自开放全球注册以来&#xff0c;一直备受跨境电商各平台卖家的关注&#xff0c;那么作为一颗跨境电商的新星&#xff0c;真的值得做吗&#xff1f; 不到一年的关注度遭到如此众多的跨境卖家追捧的平台&#xff0c;火是有他的原因的&…

【办公类-16-06】20230901大班运动场地分配表-斜线排列、5天循环、不跳节日,手动修改节日”(python 排班表系列)

背景需求&#xff1a; 大班组长发来一个“运动排班”的需求表&#xff1a;“就是和去年一样的每个班的运动排班&#xff0c;就因为今年大班变成7个班&#xff0c;删掉一个场地&#xff0c;就要重新做一份&#xff0c;不然我就用去年的那份了&#xff08;8个大班排班&#xff0…

【内网穿透】在Ubuntu搭建Web小游戏网站,并将其发布到公网访问

目录 前言 1. 本地环境服务搭建 2. 局域网测试访问 3. 内网穿透 3.1 ubuntu本地安装cpolar 3.2 创建隧道 3.3 测试公网访问 4. 配置固定二级子域名 4.1 保留一个二级子域名 4.2 配置二级子域名 4.3 测试访问公网固定二级子域名 前言 网&#xff1a;我们通常说的是互…

solved: Could not load the Qt platform plugin “xcb“

传送门 1 export QT_QPA_PLATFORMoffscreen 或者 import os os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH")2 pip uninstall opencv-python pip install opencv-python-headless

spark的数据扩展

会导致数据扩展的操作; 如何避免数据扩展; 一 countDistinct操作 1. 扩展原因 Spark的count distinct操作可能会导致数据扩展的原因是&#xff0c;它需要在执行操作之前对所有不同的值 进行分组。这意味着Spark需要将所有数据加载到内存中&#xff0c;并将其按照不同的值进行…

notepad++配合正则表达式分组模式处理文本转化为sql语句

一、正则分组知识点补充 正则分组和捕获 ()&#xff1a;用于分组和捕获子表达式。 大白话就是()匹配到的数据&#xff0c;通过美元符号加下标可以获取该数据&#xff0c;例如$1、$2, 下标从1开始。 下面的案例就采用该模式处理文本数据 二、使用正则的需求背景 有一份报表…

小米云原生文件存储平台化实践:支撑 AI 训练、大模型、容器平台多项业务

小米作为全球知名的科技巨头公司&#xff0c;已经在数百款产品中广泛应用了 AI 技术&#xff0c;这些产品包括手机、电视、智能音箱、儿童手表和翻译机等。这些 AI 应用主要都是通过小米的深度学习训练平台完成的。 在训练平台的存储方案中&#xff0c;小米曾尝试了多种不同的…

网络安全CTF比赛有哪些事?——《CTF那些事儿》告诉你

目录 前言 一、内容简介 二、读者对象 三、专家推荐 四、全书目录 前言 CTF比赛是快速提升网络安全实战技能的重要途径&#xff0c;已成为各个行业选拔网络安全人才的通用方法。但是&#xff0c;本书作者在从事CTF培训的过程中&#xff0c;发现存在几个突出的问题&#xff1…

pymysql执行insert插入操作

视频版教程 Python操作Mysql数据库之pymysql模块技术 执行修改操作&#xff0c;需要通过Connection对象调用commit()方法确认提交&#xff0c;或者 构造方法里面&#xff0c;autocommit设置True&#xff0c;自动提交 from pymysql import Connectioncon Nonetry:# 创建数据库…

C++中的静态库与动态库

文章目录 静态库构建静态库 动态库构建动态库 它们的不同参考文章 单独提这个 库&#xff0c;我想我们在coding过程中&#xff0c;可能也会知道一两个词&#xff0c;如 标准库、xx库等。库作为一组已编写好、组织好的、可复用的资源接口&#xff0c;可以被用于其他程序。很不…

Office Xml 2003转XLSX

一、使用到的依赖包 1、xelem-3.1.jar 下载地址&#xff1a;管网下载地址 2、poi-3.17.jar 下载地址&#xff1a;https://mvnrepository.com/artifact/org.apache.poi/poi 二、实现方法 1、Xml2003公式转XLSX公式算法 &#xff08;1&#xff09;Xml2003函数格式 SUM(R[-1…

Linux安装mysql数据库并实现主从搭建

一.环境说明 【环境说明】&#xff1a; 192.168.110.161 mysql-master ##网络配置到位&#xff0c;防火墙关闭&#xff0c;selinux关闭 192.168.110.162 mysql-slave ##网络配置到位&#xff0c;防火墙关闭&#xff0c;selinux关闭 两台主机&#xff0c;操作系统是centos7…

【Python入门教程】Python实现猜数字小游戏

今天跟大家分享一下很久之前自己做的一款猜数字小游戏&#xff0c;基本的循环判断语句即可实现&#xff0c;可以用来当练手或者消磨时间用。 大家在编代码的时候最重要就是先理清逻辑思路&#xff0c;例如应该套几层循环、分几个模块等等。然后在编码时可以先随意一点&#xff…

影响独立服务器稳定运行的因素

影响独立服务器稳定运行的因素 独立服务器的稳定对于网站和运行的程序来说都是最重要的因素&#xff0c;不只是简单的影响网站的速度&#xff0c;也影响搜索引擎对网站的优化。试想一下&#xff0c;客户在访问网站时&#xff0c;网页长时间打不开&#xff0c;页面崩溃会导致客户…

opencv图像像素类型转换与归一化

文章目录 opencv图像像素类型转换与归一化1、为什么对图像像素类型转换与归一化2、在OpenCV中&#xff0c;convertTo() 和 normalize() 是两个常用的图像处理函数&#xff0c;用于图像像素类型转换和归一化&#xff1b;&#xff08;1&#xff09;convertTo() 函数用于将一个 cv…

算法 三数之和-(双指针)

牛客网: BM54 题目: 数组中所有不重复的满足三数之和等于0的数&#xff0c;非递减形式。 思路: 数组不小于3。不重复非递减&#xff0c;需先排序。使用idx从0开始遍历到n-2, 如果出现num[idx]num[idx-1]的情况&#xff0c;忽略继续下一个idx&#xff1b;令left idx1, right …

现代卷积网络实战系列2:PyTorch构建训练函数、LeNet网络

&#x1f308;&#x1f308;&#x1f308;现代卷积网络实战系列 总目录 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 1、MNIST数据集处理、加载、网络初始化、测试函数 2、训练函数、PyTorch构建LeNet网络 3、PyTorch从零构建AlexNet训练MNIST数据…

ndoe.js、npm相关笔记

1、npm 全局安装 npm config get prefix 获取 npm 全局安装路径如果全局插件不能正常使用&#xff0c;看环境变量是否已经配置。没有配置则把全局安装路径配置到环境变量的path中

npm 命令

目录 初始化 搜索 安装 删除 更新 换源 查看 其他 补充 1.初始化 npm init #初始化一个package.json文件 npm init -y | npm init --yes 2.搜索 npm s jquery | npm search jquery 3.安装 npm install npm -g #更新到最新版本 npm i uniq | npm ins…