十四、流式编程(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;我们通常说的是互…

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

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

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

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

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

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

ndoe.js、npm相关笔记

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

MS933NA适用于 1MP/60fps 摄像头、37.5MHz100MHz、10 位/12 位的串化器

MS933NA 是 10 位 /12 位串化器&#xff0c;支持 37.5MHz  100MHz 时钟&#xff0c; MS933NA 广泛应用于车载摄像、医疗设备、管道探测等领域。 主要特点 ◼ 支持输入 37.5MHz 到 100MHz 的图像时钟 ◼ 单个差分对互连 ◼ 可编程数据有效负载 10 位 /12 …

django 实现:闭包表—树状结构

闭包表—树状结构数据的数据库表设计 闭包表模型 闭包表&#xff08;Closure Table&#xff09;是一种通过空间换时间的模型&#xff0c;它是用一个专门的关系表&#xff08;其实这也是我们推荐的归一化方式&#xff09;来记录树上节点之间的层级关系以及距离。 场景 我们 …

什么是关系模型? 关系模型的基本概念

关系模型由IBM公司研究员Edgar Frank Codd于1970年发表的论文中提出&#xff0c;经过多年的发展&#xff0c;已经成为目前最常用、最重要的模型之一。 在关系模型中有一些基本的概念&#xff0c;具体如下。 (1)关系(Relation)。关系一词与数学领域有关&#xff0c;它是集合基…

Xcode14.3.1打包报错Command PhaseScriptExecution failed with a nonzero exit code

真机运行编译正常,一打包就报错 rsync error: some files could not be transferred (code 23) at /AppleInternal/Library/BuildRoots/d9889869-120b-11ee-b796-7a03568b17ac/Library/Caches/com.apple.xbs/Sources/rsync/rsync/main.c(996) [sender2.6.9] Command PhaseScrip…

优化类问题概述

数学建模系列文章&#xff1a; 以下是个人在准备数模国赛时候的一些模型算法和代码整理&#xff0c;有空会不断更新内容&#xff1a; 评价模型&#xff08;一&#xff09;层次分析法&#xff08;AHP&#xff09;,熵权法&#xff0c;TOPSIS分析 及其对应 PYTHON 实现代码和例题…

数据通信——应用层(域名系统)

引言 TCP到此就告一段落&#xff0c;这也意味着传输层结束了&#xff0c;紧随其后的就是TCP/IP五层架构的应用层。操作系统、编程语言、用户的可视化界面等等都要通过应用层来体现。应用层和我们息息相关&#xff0c;我们使用电子设备娱乐或办公时&#xff0c;接触到的就是应用…

【刷题笔记9.24】LeetCode:二叉树最大深度

LeetCode&#xff1a;二叉树最大深度 1、题目描述&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 二、思路与算法 如果我们知道了左子树和右子树的最大深度 lll 和 rrr&#xff0c;…

力扣:109. 有序链表转换二叉搜索树(Python3)

题目&#xff1a; 给定一个单链表的头节点 head &#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为高度平衡的二叉搜索树。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。 来源&#xff1a;力扣&#xff08;LeetCod…

uni-app使用HBuilder X编辑器本地打包apk步骤说明

1.下载安装Android Studio 下载地址官方地址&#xff1a;Android Studio 下载文件归档 | Android 开发者 | Android Developers 安装Android SDK和Google USB Driver即可&#xff0c;后者主要是为了后期使用USB设置的&#xff0c;如果不需要可以不点。 2.下载uni-app提供…

ICMP差错包

ICMP报文分类 Type Code 描述 查询/差错 0-Echo响应 0 Echo响应报文 查询 3-目的不可达 0 目标网络不可达报文 差错 1 目标主机不可达报文 差错 2 目标协议不可达报文 差错 3 目标端口不可达报文 差错 4 要求分段并设置DF flag标志报文 差错 5 源路由…

Mac磁盘空间满了怎么办?Mac如何清理磁盘空间

你是不是发现你的Mac电脑存储越来越满&#xff0c;甚至操作系统本身就占了100多G的空间&#xff1f;这不仅影响了电脑的性能&#xff0c;而且也让你无法存储更多的重要文件和软件。别担心&#xff0c;今天这篇文章将告诉你如何清除多余的文件&#xff0c;让你的Mac重获新生。 一…

gateway之过滤器(Filter)详解

文章目录 什么是过滤器过滤器的种类局部过滤器代码示例全局过滤器代码示例 总结 什么是过滤器 在Spring Cloud中&#xff0c;过滤器&#xff08;Filter&#xff09;是一种关键的组件&#xff0c;用于在微服务架构中处理和转换传入请求以及传出响应。过滤器位于服务网关或代理中…