引入流
- 流是什么
集合是Java中使用最多的API,但集合操作卻遠(yuǎn)遠(yuǎn)算不上完美:
1.很多業(yè)務(wù)邏輯都涉及類似于數(shù)據(jù)庫的操作,比如對幾道菜按照類別進(jìn)行分組 (比如全素 菜肴),或查找出最貴的菜苛谷。你自己用迭代器重新實(shí)現(xiàn)過這些操作多少遍?
2.要是要處理大量元素又該怎么辦呢?為了提高性能,你需要并行處理,并利用多核架構(gòu)
流是Java API的新成員,它允許你以聲明性方式處理數(shù)據(jù)集合(通過查詢語句來表達(dá),而不 是臨時(shí)編寫一個(gè)實(shí)現(xiàn))
1.聲明性——更簡潔,更易讀
2.可復(fù)合——更靈活
3.可并行——性能更好
那么,流到底是什么呢?簡短的定義就是“從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列”
1.元素序列:就像集合一樣,流也提供了一個(gè)接口,可以訪問特定元素類型的一組有序 值。
2.源:流會(huì)使用一個(gè)提供數(shù)據(jù)的源,如集合、數(shù)組或輸入/輸出資源凌唬。
3.數(shù)據(jù)處理操作:流的數(shù)據(jù)處理功能支持類似于數(shù)據(jù)庫的操作,以及函數(shù)式編程語言中 的常用操作,如filter、map漏麦、reduce客税、find、match撕贞、sort等更耻。流操作可以順序執(zhí) 行,也可并行執(zhí)行。
4.流水線:很多流操作本身會(huì)返回一個(gè)流,這樣多個(gè)操作就可以鏈接起來,形成一個(gè)大的流水線捏膨。
5.內(nèi)部迭代:與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進(jìn)行的秧均。
return dishes.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.limit(3)
.collect(toList());
在本例中,我們先是對menu調(diào)用stream方法,由菜單得到一個(gè)流。數(shù)據(jù)源是菜肴列表(菜 單),它給流提供一個(gè)元素序列号涯。接下來,對流應(yīng)用一系列數(shù)據(jù)處理操作:filter目胡、map、limit 和collect链快。除了collect之外,所有這些操作都會(huì)返回另一個(gè)流,這樣它們就可以接成一條流 水線,于是就可以看作對源的一個(gè)查詢誉己。最后,collect操作開始處理流水線,并返回結(jié)果.- 流與集合
粗略地說,集合與流之間的差異就在于什么時(shí)候進(jìn)行計(jì)算。集合是一個(gè)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu), 它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值——集合中的每個(gè)元素都得先算出來才能添加到集合中久又。(你可 以往集合里加?xùn)|西或者刪東西,但是不管什么時(shí)候,集合中的每個(gè)元素都是放在內(nèi)存里的,元素 都得先算出來才能成為集合的一部分巫延。)
相比之下,流則是在概念上固定的數(shù)據(jù)結(jié)構(gòu)(你不能添加或刪除元素),其元素則是按需計(jì) 算的效五。這是一種生產(chǎn)者-消費(fèi)者的關(guān)系。從另一個(gè)角度來說,流就 像是一個(gè)延遲創(chuàng)建的集合:只有在消費(fèi)者要求的時(shí)候才會(huì)計(jì)算值.
和迭代器類似,流只能遍歷一次炉峰。遍歷完之后,我們就說這個(gè)流已經(jīng)被消費(fèi)掉了.
外部迭代和內(nèi)部迭代
使用Collection接口需要用戶去做迭代(比如用for-each),這稱為外部迭代畏妖。 相反, Streams庫使用內(nèi)部迭代——它幫你把迭代做了,還把得到的流值存在了某個(gè)地方,你只要給出 一個(gè)函數(shù)說要干什么就可以了.
-
流操作
1.中間操作
諸如filter或sorted等中間操作會(huì)返回另一個(gè)流。這讓多個(gè)操作可以連接起來形成一個(gè)查 詢疼阔。重要的是,除非流水線上觸發(fā)一個(gè)終端操作,否則中間操作不會(huì)執(zhí)行任何處理
2.終端操作
終端操作會(huì)從流的流水線生成結(jié)果
3.流使用
總而言之,流的使用一般包括三件事:
- 一個(gè)數(shù)據(jù)源(如集合)來執(zhí)行一個(gè)查詢;
- 一個(gè)中間操作鏈,形成一條流的流水線;
- 一個(gè)終端操作,執(zhí)行流水線,并能生成結(jié)果
使用流
讓Stream API管理如何處理數(shù)據(jù)戒劫。這樣Stream API就可 以在背后進(jìn)行多種優(yōu)化。使用內(nèi)部迭代的話,Stream API可以決定并行運(yùn)行你的代碼婆廊。這 要是用外部迭代的話就辦不到了,因?yàn)槟阒荒苡脝我痪€程挨個(gè)迭代迅细。
-
篩選切片
篩選各異的元素
截?cái)嗔?br> 跳過 映射
map
flatMap
查找匹配
-
歸約
如何把一個(gè)流中的元素組合起來,使用reduce操作來表達(dá)更復(fù)雜的查
詢,比如“計(jì)算菜單中的總卡路里”或“菜單中卡路里最高的菜是哪一個(gè)”。此類查詢需要將流中所有元素反復(fù)結(jié)合起來,得到一個(gè)值,比如一個(gè)Integer淘邻。
交易員Demo
(1) 找出2011年發(fā)生的所有交易,并按交易額排序(從低到高)茵典。
(2) 交易員都在哪些不同的城市工作過?
(3) 查找所有來自于劍橋的交易員,并按姓名排序。
(4) 返回所有交易員的姓名字符串,按字母順序排序宾舅。
(5) 有沒有交易員是在米蘭工作的?
(6) 打印生活在劍橋的交易員的所有交易額统阿。 (7) 所有交易中,最高的交易額是多少?
(8) 找到交易額最小的交易。數(shù)值流
構(gòu)建流
1.值創(chuàng)建類
2.數(shù)組生成流
3.文件生成流
4.由函數(shù)生成流筹我,創(chuàng)建無限流
用流收集數(shù)據(jù)
- 歸約和匯總
collect是一個(gè)歸約操作,就像reduce一樣可以接 受各種做法作為參數(shù),將流中的元素累積成一個(gè)匯總結(jié)果扶平。具體的做法是通過定義新的 Collector接口來定義的,因此區(qū)分Collection、Collector和collect是很重要的
下面是一些查詢的例子,看看你用collect和收集器能夠做什么蔬蕊。
1.對一個(gè)交易列表按貨幣分組,獲得該貨幣的所有交易額總和(返回一個(gè)Map<Currency,Integer>)结澄。
2.將交易列表分成兩組:貴的和不貴的(返回一個(gè)Map<Boolean, List<Transaction>>)。
3.創(chuàng)建多級分組,比如按城市對交易分組,然后進(jìn)一步按照貴或不貴分組(返回一個(gè) Map<Boolean, List<Transaction>>)
前一個(gè)例子清楚地展示了函數(shù)式編程相對于指令式編程的一個(gè)主要優(yōu)勢:你只需指出希望的 結(jié)果——“做什么”,而不用操心執(zhí)行的步驟——“如何做”岸夯。在上一個(gè)例子里,傳遞給collect 方法的參數(shù)是Collector接口的一個(gè)實(shí)現(xiàn),也就是給Stream中元素做匯總的方法麻献,toList只是說“按順序給每個(gè)元素生成一個(gè)列表”;在本例中,groupingBy說的是“生成一個(gè) Map,它的鍵是(貨幣)桶,值則是桶中那些元素的列表”。
預(yù)定義收集器
預(yù)定義收集器的功能,也就是那些可以從Collectors 類提供的工廠方法(例如groupingBy)創(chuàng)建的收集器猜扮。它們主要提供了三大功能:
1.將流元素歸約和匯總為一個(gè)值
2.元素分組
3.元素分區(qū)歸約 匯總
廣義的歸約匯總
reducing方法創(chuàng)建的收集器
起始值
轉(zhuǎn)換函數(shù)
累計(jì)函數(shù)-
分組
分區(qū)
分區(qū)是分組的特殊情況:由一個(gè)謂詞(返回一個(gè)布爾值的函數(shù))作為分類函數(shù),它稱分區(qū)函 數(shù)赎瑰。分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組Map的鍵類型是Boolean,于是它最多可以 分為兩組——true是一組,false是一組-
收集器接口
自定義歸約操作
并行數(shù)據(jù)處理與性能
- 并行流
在Java 7之前,并行處理數(shù)據(jù)集合非常麻煩破镰。第一,你得明確地把包含數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)分成若干子部分压储。第二鲜漩,你要給每個(gè)子部分分配一個(gè)獨(dú)立的線程。第三集惋,你需要在恰當(dāng)?shù)臅r(shí)候?qū)λ鼈冞M(jìn)行同步來避免不希望出現(xiàn)的競爭條件孕似,等待所有線程完成,最后把這些部分結(jié)果合并起來刮刑。 Java 7引入了一個(gè)叫作分支/合并的框架喉祭,讓這些操作更穩(wěn)定养渴、更不易出錯(cuò)
高效使用并行流
把順序流轉(zhuǎn)成并行流輕而易舉,但卻不一定是好事泛烙。我們在本節(jié)中
已經(jīng)指出理卑,并行流并不總是比順序流快。此外蔽氨,并行流有時(shí)候會(huì)和你的直覺不一致藐唠,所
以在考慮選擇順序流還是并行流時(shí),第一個(gè)也是最重要的建議就是用適當(dāng)?shù)幕鶞?zhǔn)來檢查
其性能
1.留意裝箱鹉究。自動(dòng)裝箱和拆箱操作會(huì)大大降低性能宇立。 Java 8中有原始類型流(IntStream、
LongStream自赔、 DoubleStream)來避免這種操作妈嘹,但凡有可能都應(yīng)該用這些流
2.有些操作本身在并行流上的性能就比順序流差。特別是limit和findFirst等依賴于元
素順序的操作绍妨,它們在并行流上執(zhí)行的代價(jià)非常大
3.還要考慮流的操作流水線的總計(jì)算成本
4.對于較小的數(shù)據(jù)量润脸,選擇并行流幾乎從來都不是一個(gè)好的決定
5.考慮流背后的數(shù)據(jù)結(jié)構(gòu)是否易于分解
6.考慮終端操作中合并步驟的代價(jià)是大是小
-
fork/join
Spliterator