引入需求
從一段代碼引入
return s.length() - (int) s.chars().filter(c -> c == 'S').count();
其中 (int) s.chars().filter(c -> c == 'S').count();
計算了字符串 s 中字符 ‘S’ 的數(shù)量。
下面解讀其原理:
代碼原理解讀
s.chars()
Java 中的 String 類的 chars() 方法是用來將字符串轉(zhuǎn)換為 IntStream 的一種方法。IntStream是一個表示 int 值序列的流。
該方法不接受任何參數(shù),返回一個 IntStream,其中每個元素是字符串中對應(yīng)位置的 char 值。
String s = "Hello";
IntStream chars = s.chars();
chars.forEach(System.out::println);
// 輸出結(jié)果為
72
101
108
108
111
IntStream filter?(IntPredicate predicate)
本題中使用的是 IntStream 類的 .filter() 方法 (除此之外其它類有的也會有 .filter() 方法)
Java 中的 .filter() 方法是一個中間操作,它會返回一個新的流,該流由該流中與給定 predicate 匹配的元素組成。(可以認(rèn)為這是一個過濾器)
比如本題就是只保留了 c == 'S'
的元素。
long count()
注意返回值是 long
基本數(shù)據(jù)類型。
Java 中的 .count() 方法是一個終端操作,它返回流中元素的數(shù)量。它是一種特殊的歸約操作,它將一系列輸入元素組合成一個單一的結(jié)果。例如,如果我們想要統(tǒng)計一個整數(shù)列表中有多少個偶數(shù),我們可以這樣寫:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6); //整數(shù)列表
long count = list.stream() //創(chuàng)建一個流
.filter(n -> n % 2 == 0) //根據(jù)謂詞篩選出偶數(shù)
.count(); //計算流中元素的數(shù)量
System.out.println(count); //輸出結(jié)果 為 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)
返回一個由該流的元素組成的流,并在從結(jié)果流中消耗元素時對每個元素執(zhí)行所提供的操作。
此方法的存在主要是為了支持調(diào)試
即
peek() 方法不會改變流中元素的值或順序,也不會影響流的終端操作。它只是在流中插入了一個額外的操作,用于觀察或記錄元素。它返回一個新的 IntStream 對象,因此可以和其他的中間操作或終端操作鏈?zhǔn)秸{(diào)用。
可以看下面的代碼示例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();
// 控制臺輸出結(jié)果為:
Filtered value: 3
Mapped value: 9
Filtered value: 4
Mapped value: 16
代碼示例2:
IntStream.of(5, 3, 1, 4, 2) //創(chuàng)建一個整數(shù)流
.peek(n -> System.out.println("Original: " + n)) //打印原始值
.sorted() //排序
.peek(n -> System.out.println("Sorted: " + n)) //打印排序后的值
.sum(); //求和
// 控制臺輸出結(jié)果為:
Original: 5
Original: 3
Original: 1
Original: 4
Original: 2
Sorted: 1
Sorted: 2
Sorted: 3
Sorted: 4
Sorted: 5
這時候會有疑問:為什么兩段代碼示例的控制臺輸出結(jié)果順序好像不符合預(yù)期?
A:這是因為流的操作是惰性的,也就是說,只有當(dāng)終端操作(如 sum() )需要時,才會真正執(zhí)行中間操作(如 filter() , map() , peek() )。
因此,流中的每個元素都會按照管道中的順序依次執(zhí)行所有的中間操作,而不是先執(zhí)行完一個中間操作再執(zhí)行下一個。所以,在代碼示例 1 中,對于第一個元素3,它會先被 filter() ,然后被 peek() ,然后被 map() ,然后再被 peek() ,最后才會被 sum() 。對于第二個元素4,它也會經(jīng)歷同樣的過程。因此,Mapped value: 9 會輸出在 Filtered value: 4 之前。
在上文中 count() 方法的文檔中有這樣一段代碼:
IntStream s = IntStream.of(1, 2, 3, 4);
long count = s.peek(System.out::println).count();
這段代碼對應(yīng)著控制臺沒有任何輸出,這是因為count() 方法是一個短路操作,也就是說,它不需要遍歷所有的元素就可以得到結(jié)果。因此,對于一個有限的流, count() 方法會直接返回流中元素的數(shù)量,而不會觸發(fā)任何中間操作(如 peek() )。這是 Java 9 中對 count() 方法的一個優(yōu)化,以提高性能。
流操作和管道
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.
中文翻譯如下:(這部分信息量很大很重要!
)
流操作分為中間操作和終端操作,并結(jié)合起來形成流管道。流管道由一個源(例如Collection、數(shù)組、生成器函數(shù)或I/O通道)組成;然后是零個或多個中間操作,例如Stream.filter或Stream.map;以及諸如Stream.forEach或Stream.reduce之類的終端操作。
中間操作返回一個新流。他們總是懶惰;執(zhí)行諸如filter()之類的中間操作實際上并不執(zhí)行任何過濾,而是創(chuàng)建一個新流,當(dāng)遍歷該新流時,該新流包含與給定謂詞匹配的初始流的元素。在執(zhí)行管道的終端操作之前,管道源的遍歷不會開始。
終端操作,如Stream.forEach或IntStream.sum,可能會遍歷流以產(chǎn)生結(jié)果或副作用。在執(zhí)行終端操作之后,流管道被認(rèn)為已被消耗,并且不能再使用;如果需要再次遍歷同一數(shù)據(jù)源,則必須返回到數(shù)據(jù)源以獲得新的流。在幾乎所有情況下,終端操作都很渴望,在返回之前完成對數(shù)據(jù)源的遍歷和對管道的處理。只有終端操作迭代器()和拆分器()不是;這些是作為“逃生通道”提供的,以便在現(xiàn)有操作不足以完成任務(wù)的情況下,實現(xiàn)任意客戶端控制的管道遍歷。
懶散地處理流可以實現(xiàn)顯著的效率;在像上面的filter map sum示例這樣的流水線中,過濾、映射和求和可以被融合到數(shù)據(jù)的單個傳遞中,具有最小的中間狀態(tài)。懶惰還可以避免在不必要的時候檢查所有數(shù)據(jù);對于諸如“查找長度超過1000個字符的第一個字符串”之類的操作,只需檢查剛好足夠的字符串即可找到具有所需特性的字符串,而無需檢查源中所有可用的字符串。(當(dāng)輸入流是無限的而不僅僅是大的時,這種行為變得更加重要。)
中間操作進(jìn)一步分為無狀態(tài)操作和有狀態(tài)操作。無狀態(tài)操作,如filter和map,在處理新元素時不會保留以前看到的元素的狀態(tài)——每個元素都可以獨立于對其他元素的操作進(jìn)行處理。在處理新元素時,有狀態(tài)的操作(如distinct和sorted)可以合并以前看到的元素的狀態(tài)。
有狀態(tài)操作可能需要在生成結(jié)果之前處理整個輸入。例如,在看到流的所有元素之前,對流進(jìn)行排序無法產(chǎn)生任何結(jié)果。因此,在并行計算下,一些包含有狀態(tài)中間操作的管道可能需要對數(shù)據(jù)進(jìn)行多次傳遞,或者可能需要緩沖重要數(shù)據(jù)。包含完全無狀態(tài)中間操作的管道可以在一次過程中處理,無論是順序的還是并行的,只需最少的數(shù)據(jù)緩沖。文章來源:http://www.zghlxwxcb.cn/news/detail-521682.html
此外,一些操作被認(rèn)為是短路操作。如果在無限輸入的情況下,中間運算可能會產(chǎn)生有限的流,那么它就是短路。如果在無限輸入的情況下,終端操作可能在有限時間內(nèi)終止,則終端操作是短路。在管道中進(jìn)行短路操作是處理無限流在有限時間內(nèi)正常終止的必要條件,但不是充分條件。文章來源地址http://www.zghlxwxcb.cn/news/detail-521682.html
到了這里,關(guān)于【Java語法小記】求字符串中某個字符的數(shù)量——IntStream流的使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!