【Java语法小记】求字符串中某个字符的数量——IntStream流的使用

文章目录

  • 引入需求
  • 代码原理解读
    • s.chars()
    • IntStream filter​(IntPredicate predicate)
    • long count()
    • 补充:IntStream peek​(IntConsumer action)
  • 流操作和管道

引入需求

从一段代码引入

return s.length() - (int) s.chars().filter(c -> c == 'S').count(); 

其中 (int) s.chars().filter(c -> c == 'S').count(); 计算了字符串 s 中字符 ‘S’ 的数量。
下面解读其原理:

代码原理解读

s.chars()

在这里插入图片描述
Java 中的 String 类的 chars() 方法是用来将字符串转换为 IntStream 的一种方法。IntStream是一个表示 int 值序列的流。

该方法不接受任何参数,返回一个 IntStream,其中每个元素是字符串中对应位置的 char 值。

String s = "Hello";
IntStream chars = s.chars();
chars.forEach(System.out::println);// 输出结果为
72
101
108
108
111

IntStream filter​(IntPredicate predicate)

本题中使用的是 IntStream 类的 .filter() 方法 (除此之外其它类有的也会有 .filter() 方法)
在这里插入图片描述
Java 中的 .filter() 方法是一个中间操作,它会返回一个新的流,该流由该流中与给定 predicate 匹配的元素组成。(可以认为这是一个过滤器)

比如本题就是只保留了 c == 'S' 的元素。

long count()

在这里插入图片描述

注意返回值是 long 基本数据类型。

Java 中的 .count() 方法是一个终端操作,它返回流中元素的数量。它是一种特殊的归约操作,它将一系列输入元素组合成一个单一的结果。例如,如果我们想要统计一个整数列表中有多少个偶数,我们可以这样写:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6); //整数列表
long count = list.stream() //创建一个流.filter(n -> n % 2 == 0) //根据谓词筛选出偶数.count(); //计算流中元素的数量
System.out.println(count); //输出结果 为 3

补充:IntStream peek​(IntConsumer action)

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/IntStream.html#peek(java.util.function.IntConsumer)
在这里插入图片描述
返回一个由该流的元素组成的流,并在从结果流中消耗元素时对每个元素执行所提供的操作。

此方法的存在主要是为了支持调试

peek() 方法不会改变流中元素的值或顺序,也不会影响流的终端操作。它只是在流中插入了一个额外的操作,用于观察或记录元素。它返回一个新的 IntStream 对象,因此可以和其他的中间操作或终端操作链式调用。

可以看下面的代码示例1:

IntStream.of(1, 2, 3, 4).filter(e -> e > 2).peek(e -> System.out.println("Filtered value: " + e)).map(e -> e * e).peek(e -> System.out.println("Mapped value: " + e)).sum();// 控制台输出结果为:
Filtered value: 3
Mapped value: 9
Filtered value: 4
Mapped value: 16

代码示例2:

IntStream.of(5, 3, 1, 4, 2) //创建一个整数流.peek(n -> System.out.println("Original: " + n)) //打印原始值.sorted() //排序.peek(n -> System.out.println("Sorted: " + n)) //打印排序后的值.sum(); //求和// 控制台输出结果为:
Original: 5
Original: 3
Original: 1
Original: 4
Original: 2
Sorted: 1
Sorted: 2
Sorted: 3
Sorted: 4
Sorted: 5

这时候会有疑问:为什么两段代码示例的控制台输出结果顺序好像不符合预期
A:这是因为流的操作是惰性的,也就是说,只有当终端操作(如 sum() )需要时,才会真正执行中间操作(如 filter() , map() , peek() )
因此,流中的每个元素都会按照管道中的顺序依次执行所有的中间操作,而不是先执行完一个中间操作再执行下一个。所以,在代码示例 1 中,对于第一个元素3,它会先被 filter() ,然后被 peek() ,然后被 map() ,然后再被 peek() ,最后才会被 sum() 。对于第二个元素4,它也会经历同样的过程。因此,Mapped value: 9 会输出在 Filtered value: 4 之前。


在上文中 count() 方法的文档中有这样一段代码:

IntStream s = IntStream.of(1, 2, 3, 4);
long count = s.peek(System.out::println).count();

这段代码对应着控制台没有任何输出,这是因为count() 方法是一个短路操作,也就是说,它不需要遍历所有的元素就可以得到结果。因此,对于一个有限的流, count() 方法会直接返回流中元素的数量,而不会触发任何中间操作(如 peek() )。这是 Java 9 中对 count() 方法的一个优化,以提高性能。

流操作和管道

Stream operations and pipelines
这部分的英文原文如下:

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Terminal operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not; these are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.

Processing streams lazily allows for significant efficiencies; in a pipeline such as the filter-map-sum example above, filtering, mapping, and summing can be fused into a single pass on the data, with minimal intermediate state. Laziness also allows avoiding examining all the data when it is not necessary; for operations such as “find the first string longer than 1000 characters”, it is only necessary to examine just enough strings to find one that has the desired characteristics without examining all of the strings available from the source. (This behavior becomes even more important when the input stream is infinite and not merely large.)

Intermediate operations are further divided into stateless and stateful operations. Stateless operations, such as filter and map, retain no state from previously seen element when processing a new element – each element can be processed independently of operations on other elements. Stateful operations, such as distinct and sorted, may incorporate state from previously seen elements when processing new elements.

Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream. As a result, under parallel computation, some pipelines containing stateful intermediate operations may require multiple passes on the data or may need to buffer significant data. Pipelines containing exclusively stateless intermediate operations can be processed in a single pass, whether sequential or parallel, with minimal data buffering.

Further, some operations are deemed short-circuiting operations. An intermediate operation is short-circuiting if, when presented with infinite input, it may produce a finite stream as a result. A terminal operation is short-circuiting if, when presented with infinite input, it may terminate in finite time. Having a short-circuiting operation in the pipeline is a necessary, but not sufficient, condition for the processing of an infinite stream to terminate normally in finite time.


中文翻译如下:(这部分信息量很大很重要!

流操作分为中间操作终端操作,并结合起来形成流管道。流管道由一个源(例如Collection、数组、生成器函数或I/O通道)组成;然后是零个或多个中间操作,例如Stream.filter或Stream.map;以及诸如Stream.forEach或Stream.reduce之类的终端操作。

中间操作返回一个新流。他们总是懒惰;执行诸如filter()之类的中间操作实际上并不执行任何过滤,而是创建一个新流,当遍历该新流时,该新流包含与给定谓词匹配的初始流的元素。在执行管道的终端操作之前,管道源的遍历不会开始

终端操作,如Stream.forEach或IntStream.sum,可能会遍历流以产生结果或副作用。在执行终端操作之后,流管道被认为已被消耗,并且不能再使用;如果需要再次遍历同一数据源,则必须返回到数据源以获得新的流。在几乎所有情况下,终端操作都很渴望,在返回之前完成对数据源的遍历和对管道的处理。只有终端操作迭代器()和拆分器()不是;这些是作为“逃生通道”提供的,以便在现有操作不足以完成任务的情况下,实现任意客户端控制的管道遍历。

懒散地处理流可以实现显著的效率;在像上面的filter map sum示例这样的流水线中,过滤、映射和求和可以被融合到数据的单个传递中,具有最小的中间状态懒惰还可以避免在不必要的时候检查所有数据;对于诸如“查找长度超过1000个字符的第一个字符串”之类的操作,只需检查刚好足够的字符串即可找到具有所需特性的字符串,而无需检查源中所有可用的字符串。(当输入流是无限的而不仅仅是大的时,这种行为变得更加重要。)

中间操作进一步分为无状态操作有状态操作。无状态操作,如filter和map,在处理新元素时不会保留以前看到的元素的状态——每个元素都可以独立于对其他元素的操作进行处理。在处理新元素时,有状态的操作(如distinct和sorted)可以合并以前看到的元素的状态。

有状态操作可能需要在生成结果之前处理整个输入。例如,在看到流的所有元素之前,对流进行排序无法产生任何结果。因此,在并行计算下,一些包含有状态中间操作的管道可能需要对数据进行多次传递,或者可能需要缓冲重要数据。包含完全无状态中间操作的管道可以在一次过程中处理,无论是顺序的还是并行的,只需最少的数据缓冲。

此外,一些操作被认为是短路操作。如果在无限输入的情况下,中间运算可能会产生有限的流,那么它就是短路。如果在无限输入的情况下,终端操作可能在有限时间内终止,则终端操作是短路。在管道中进行短路操作是处理无限流在有限时间内正常终止的必要条件,但不是充分条件。


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

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

相关文章

cmake流程控制---cmake数学计算操作和if命令

目录 cmake 数学计算操作 demo if命令 基本表达式 逻辑表达式(NOT AND OR) 比较表达式 正则表达式 demo 文件系统相关 判断是否存在的表达式 cmake 数学计算操作 有时候我们需要对cmake变量之间进行数学运算,这时候cmake提供了math()这个命令,命令格式如下: math(…

SQL-每日一题【584.寻找用户推荐人】

题目 给定表 customer &#xff0c;里面保存了所有客户信息和他们的推荐人。 写一个查询语句&#xff0c;返回一个客户列表&#xff0c;列表中客户的推荐人的编号都 不是 2。 对于上面的示例数据&#xff0c;结果为&#xff1a; 解题思路 1.题目要求查询列表中客户的推荐人的…

RTL8309M实现VLAN功能-驱动编进内核

部分硬件实现图 一、使内核支持802.1Q功能 1、使用内核kernel版本4.19.232 2、make menuconfig 配置内核 3、进入Networking support 4、进入Networking options 5、把这些都编进内核 6、点击保存退出 CONFIG_GARPy CONFIG_MRPy CONFIG_BRIDGE_VLAN_FILTERINGy CONFIG_VLAN…

Go语言程序设计(五)切片

一、切片的定义 在Go语言中,切片(Slice)是数组的一个引用,它会生成一个指向数组的指针,并通过切片长度关联到底层数组部分或者全部元素。切片还提供了一系列对数组的管理功能(append、copy)&#xff0c;可以随时动态扩充存储空间&#xff0c;并且可以被随意传递而不会导致所管理…

C++数据结构笔记(7)——队列的顺序结构实现

1.队列&#xff0c;和现实生活中的规则类似&#xff0c;先进先出 2.队尾只允许元素进入&#xff0c;队头只允许元素退出 3.用数组来实现队列的顺序存储&#xff0c;无论哪一段都可以作为队头或者队尾 SeqQueue.h头文件 #ifndef SEQQUEUE_H #define SEQQUEUE_H #include<…

Linux——进程信号详解

目录 一.进程信号的理解 1.1定义&#xff1a; 1.2举例&#xff1a; 1.3总结&#xff1a; 二.进程信号地使用&#xff1a; 2.1信号种类&#xff1a; 2.2而操作系统向进程发送信号地方式有四种&#xff1a; 2.2.1以键盘的方式向进程发送信号 接下来介绍一个系统调用函数sign…

chrome开发调试小技巧—Replay XHR(重新请求)

一、需求 想要验证一个ajax请求&#xff0c;需要每次都需要在页面点几次才会触发或者刷新页面&#xff0c;着急调试看效果时&#xff0c;可以通过chrome的Replay XHR功能直接同参数重新请求ajax 二、实现 chrome调试工具network下找到要重新发起的ajax请求&#xff0c;右键找…

python读写excel利器:xlwings从入门到精通

python读写excel利器&#xff1a;xlwings 从入门到精通 目录 安装和使用 基础操作 操作工作簿操作工作表读取单元格写入(单元格赋值)range的操作&#xff08;range常用的api&#xff09; 写入一行或一列Excel数据(函数式&#xff09;使用range(\A1\).api.AddComment(\comments…

力扣 332. 重新安排行程

题目来源&#xff1a;https://leetcode.cn/problems/reconstruct-itinerary/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a; 这道题目有几个难点&#xff1a; 一个行程中&#xff0c;如果航班处理不好容易变成一个圈&#xff0c;成为死循环。解决&am…

ue4:Dota总结_BP_CameraPawn篇

设计wasd移动&#xff1a; 鼠标拖动视口&#xff1a; 鼠标滚轮调整远近&#xff1a; Beginplay&#xff1a; qe按键旋转&#xff1a; 变量&#xff1a;

word@制表位和列数据对齐@填空下划线制作

文章目录 refs制表位(tab stop)制表位类型 制作对其的下划线填空表单&#x1f47a;利用前导符代替下划线制作待填空下划线 制表位对其列数据模拟简单表格测试数据设置引线使用标尺设置 FAQ refs Insert or add tab stops - Microsoft SupportUsing the ruler in Word - Micros…

【Django】列表数据Paginatior分页,动态返回页码,显示当前页、总页数、跳转页

问题 1、当返回数据较多,如设置每页展示10条,数据接近200条,返回页码范围1~20,前端每个页码都显示的话,就会出现页码超出当前页面,被遮挡的页码无法操作和显示不美观; 2、列表的所在页码,总分页数,跳转不在动态页面的页数 解决 在使用paginator转化为Page对象后,获…