為什么需要 Stream
Stream 作為 Java 8 的一大亮點癌椿,它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是對集合(Collection)對象功能的增強屏轰,它專注于對集合對象進行各種非常便利瓤球、高效的聚合操作(aggregate operation)休涤,或者大批量數據操作 (bulk data operation)。Stream API 借助于同樣新出現的 Lambda 表達式诫舅,極大的提高編程效率和程序可讀性羽利。同時它提供串行和并行兩種模式進行匯聚操作,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢刊懈,使用 fork/join 并行方式來拆分任務和加速處理過程这弧。通常編寫并行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就可以很方便地寫出高性能的并發(fā)程序虚汛。所以說匾浪,Java 8 中首次出現的 java.util.stream 是一個函數式語言+多核時代綜合影響的產物。
什么是流
Stream 不是集合元素卷哩,它不是數據結構并不保存數據蛋辈,它是有關算法和計算的,它更像一個高級版本的 Iterator殉疼。原始版本的 Iterator梯浪,用戶只能顯式地一個一個遍歷元素并對其執(zhí)行某些操作;高級版本的 Stream瓢娜,用戶只要給出需要對其包含的元素執(zhí)行什么操作,比如 “過濾掉長度大于 10 的字符串”礼预、“獲取每個字符串的首字母”等眠砾,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換托酸。
Stream 就如同一個迭代器(Iterator)褒颈,單向,不可往復励堡,數據只能遍歷一次谷丸,遍歷過一次后即用盡了,就好比流水從面前流過应结,一去不復返刨疼。
而和迭代器又不同的是,Stream 可以并行化操作鹅龄,迭代器只能命令式地揩慕、串行化操作。顧名思義扮休,當使用串行方式去遍歷時迎卤,每個 item 讀完后再讀下一個 item。而使用并行去遍歷時玷坠,數據會被分成多個段蜗搔,其中每一個都在不同的線程中處理劲藐,然后將結果一起輸出。Stream 的并行操作依賴于 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程樟凄。Java 的并行 API 演變歷程基本如下:
1.0-1.4 中的 java.lang.Thread
5.0 中的 java.util.concurrent
6.0 中的 Phasers 等
7.0 中的 Fork/Join 框架
8.0 中的 Lambda
Stream 的另外一大特點是瘩燥,數據源本身可以是無限的。
流的構成
當我們使用一個流的時候不同,通常包括三個基本步驟:
獲取一個數據源(source)→ 數據轉換→執(zhí)行操作獲取想要的結果厉膀,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換)二拐,這就允許對其操作可以像鏈條一樣排列服鹅,變成一個管道,如下圖所示百新。
圖 1. 流管道 (Stream Pipeline) 的構成
有多種方式生成 Stream Source:
從 Collection 和數組
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of()
從 BufferedReader
java.io.BufferedReader.lines()
靜態(tài)工廠
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
自己構建
java.util.Spliterator
其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
流的操作類型分為兩種:
Intermediate:一個流可以后面跟隨零個或多個 intermediate 操作企软。其目的主要是打開流,做出某種程度的數據映射/過濾饭望,然后返回一個新的流仗哨,交給下一個操作使用。這類操作都是惰性化的(lazy)铅辞,就是說厌漂,僅僅調用到這類方法,并沒有真正開始流的遍歷斟珊。
Terminal:一個流只能有一個 terminal 操作苇倡,當這個操作執(zhí)行后,流就被使用“光”了囤踩,無法再被操作旨椒。所以這必定是流的最后一個操作。Terminal 操作的執(zhí)行堵漱,才會真正開始流的遍歷综慎,并且會生成一個結果,或者一個 side effect勤庐。
在對于一個 Stream 進行多次轉換操作 (Intermediate 操作)示惊,每次都對 Stream 的每個元素進行轉換,而且是執(zhí)行多次埃元,這樣時間復雜度就是 N(轉換次數)個 for 循環(huán)里把所有操作都做掉的總和嗎涝涤?其實不是這樣的,轉換操作都是 lazy 的岛杀,多個轉換操作只會在 Terminal 操作的時候融合起來阔拳,一次循環(huán)完成。我們可以這樣簡單的理解,Stream 里有個操作函數的集合糊肠,每次轉換操作就是把轉換函數放入這個集合中辨宠,在 Terminal 操作的時候循環(huán) Stream 對應的集合,然后對每個元素執(zhí)行所有的函數货裹。
還有一種操作被稱為?short-circuiting嗤形。用以指:
對于一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream弧圆,但返回一個有限的新 Stream赋兵。
對于一個 terminal 操作,如果它接受的是一個無限大的 Stream搔预,但能在有限的時間計算出結果霹期。
當操作一個無限大的 Stream,而又希望在有限時間內完成操作拯田,則在管道內擁有一個 short-circuiting 操作是必要非充分條件历造。