最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

關(guān)于 Java8 的 stream 中的 reduce 操作

2022-03-01 23:50 作者:友紀V-入OP  | 我要投稿

該筆記編寫時并未參照官方文檔,所以在一些地方可能會有錯漏。

在之前再次學(xué)習(xí)折疊操作的時候,我曾做了一些筆記,并且使用 js 編寫了一些示例。當時本打算同時也介紹一下 Java8 的 stream 中提供的 reduce 方法(以下簡稱 java8-reduce),但發(fā)現(xiàn)其行為和 js 中的相去甚遠,所以先略過了。Java為了高性能和并發(fā)支持,它的 reduce 方法是經(jīng)過大量優(yōu)化的,也引入了自己獨有的所謂 Combiner,非常具有Java特色。

如果要說 java8-reduce 的最明顯的特點,我認為有兩個——

首先,它非常合適同 Java 的可變集合一起工作(collect 方法),這在其他語言里是見不到的,這樣既符合 java 傳統(tǒng)的編程模式,也能保證足夠的性能,甚至在并發(fā)情況下也能夠應(yīng)付。

然后,它允許并發(fā)的 reduce 操作;java8-reduce 能把集合切分成多個部分,對每個部分并行地執(zhí)行 reduce 操作,并通過所謂的 combiner 函數(shù)兩兩組合,得到最終結(jié)果;但顯然使用并行的 reduce 操作時必須符合特定約束,以保證其能正確執(zhí)行,無關(guān)切分情況或處理器核心數(shù)量。下面挨個點名。

The easiest way

最簡單的 reduce 操作就是結(jié)果值和集合內(nèi)的值同類型的 reduce 了,比如對集合求積,求和,Java 中干這事一樣簡單——

但 Java 未提供 reduceRight 方法,想必是認為其應(yīng)用范圍不廣,事實也確實如此。

Parallel: Where thing goes wrong

上面是串行流,它的行為同其它語言一致,但若是并行流呢?試試下面的代碼——

結(jié)果是什么?如果是串行流的話,結(jié)果是肯定的——7,但這是并行流,我的電腦的結(jié)果是 9。也可以去嘗試一下其它結(jié)果,最后會發(fā)現(xiàn),只要 zero 參數(shù)(java 叫做 identity,這個名字興許能給我們啟發(fā))不為 0,最后并行得到的結(jié)果和串行必然不一樣。

那么,并行流的 reduce 究竟是怎么跑的?

我們知道,串行流的 reduce 可以描述成把集合中各元素通過一個二元操作符相連接,比如上面的求集合的和可以寫成——

但是并行流顯然不是這樣干的,總的來說,并行流的 reduce 操作,會先把流切分成多個部分(具體切分數(shù)目由處理器核心數(shù)量決定),然后對每個部分各自并行執(zhí)行 reduce 操作,然后再兩兩進行 Combine 操作,得到最終結(jié)果,這里的每個切分的部分,可以把它稱作ReduceTask,比如對[1,2,3,4,5].reduceParr(1, plus),它的計算過程可能是這樣的——

  1. [1, 2, 3, 4, 5]切分成四段,[1],[2],[3][4, 5]

  2. 對每一段,并行調(diào)用 reduce 方法,即[1].reduce(1, plus),[2].reduce(1, plus)……得到2,3,4,10。

  3. 對結(jié)果兩兩執(zhí)行 combine 操作,這里的 combine 操作即 acc 操作,即 plus,combine(combine(2, 3), combine(4, 10))

  4. 得到結(jié)果——19。(實踐好像是分了 5 段,因此結(jié)果是 20)

具體流程是我們不應(yīng)該關(guān)心的,我們只需要知道,各個部分會分別 reduce,然后會兩兩 combine,最終得到最終結(jié)果。這里的“兩兩 combine”不是說從左往右依次 combine(這不就又是一個串行的 reduce 嘛),想象一下歸并排序的迭代版,它自底向上,每次 combine 的都是更“大”的值。

對于同類型的 reduce 操作,combine 函數(shù)同 acc 函數(shù)相同,所以[1,2,3].reduce(0, Integer::sum)也可以描述成[1,2,3].reduce(0, Integer::sum, Integer::sum),表示它的合并和積累函數(shù)都是 sum。

非同類型的 reduce

只有在非同類型的 reduce 操作中,combine 才會明確顯示出來——它需要用戶主動去定義,但我們先來看看 Java 特色的 reduce 操作。

非同類型的 reduce 操作有兩個方法可用——

  • reduce(identity : U, acc : (U, T) => U, combiner : (U, U) => U)

  • collect(supplier : () => U, acc : (U, T) => (), combiner : (U, U) => ())

前者(reduce)用于累積的類型是不可變值的情況;后者(collect)用于累積的類型是可變值的情況。

reduce 方法

我們先來看看 reduce,下面的操作將集合反轉(zhuǎn)——

在這里,第三個參數(shù)就是所謂的 combiner,這里因為是串行流,所以 combiner 不會被調(diào)用,可以直接返回 null。但 combiner 本身不能為 null,否則會拋空指針異常。

但若是并行流呢?我們憑第一印象,大概會這么寫——

但是這樣是無法生效的,如果試圖對該集合進行輸出,它在輸出時會拋出空指針異常!這說明它的結(jié)構(gòu)被破壞了,我們遇到了線程安全問題!

但為什么會這樣呢?原來,reduce 方法中所使用的 identity,會被每一個 ReduceTask 都共用!并且我們在 acc 函數(shù)中原樣返回了累積值,因此它會被持續(xù)使用下去,如果在 combiner 中試圖判斷a == b,它也將是 true,因為 a 和 b 是同一個對象!

因此,reduce 方法只適合不可變對象,或者我們可以每次返回值都不改變原值,而是返回一個新的值,新的引用,但這對性能是極大的損耗,只有 string 這樣的不可變類在 reduce 方法上才能得到運用。

collect 方法

這時,使用collect方法就是一個更好的選擇,我們再看一看 collect 的簽名——

這簽名實際上把所有細節(jié)都描述出來了——我們通過一個 supplier,來讓每一個 ReduceTask 都能拿到不一樣的引用,從而避免共享數(shù)據(jù)問題;acc 和 combiner 都是沒有返回值的,因此顯然我們需要通過修改累積值來完成積累和合并操作。下面使用 collect 來進行一個 count 操作——

That’s it!了解這么多就足夠了。使用原則是,當使用值類型,或不可變類型,如 String,Scala,Kotlin 的不可變集合,基本類型等的時候,使用 reduce 方法;使用引用類型,可變類型的時候,使用 collect 方法。

雖然 combiner,acc,初始值的設(shè)置要遵循的一定的規(guī)律,但給出統(tǒng)一和容易理解的表述并不容易,具體問題具體分析吧。

關(guān)于 Java8 的 stream 中的 reduce 操作的評論 (共 條)

分享到微博請遵守國家法律
海林市| 裕民县| 遵义县| 南宁市| 长岛县| 上饶县| 肇源县| 阜城县| 滨州市| 惠水县| 昌图县| 黔南| 华宁县| 江北区| 长沙市| 剑阁县| 剑川县| 南漳县| 江津市| 遵义县| 武川县| 郁南县| 恩平市| 蓬莱市| 彰化县| 大渡口区| 磐安县| 博白县| 新蔡县| 郸城县| 德兴市| 雅安市| 石家庄市| 永和县| 汉源县| 绵竹市| 晴隆县| 富宁县| 建水县| 广河县| 义马市|