表格內容來自https://docs.oracle.com/javase/8/docs/api/ Package java.util.stream 一節部分原文內容的翻譯 |
int sum = widgets.stream() .filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum();
流操做被劃分爲中間和終端操做,並組合成流管道。
一條Stream管道由一個源(如一個集合、一個數組、一個生成器函數或一個i/o通道)組成;
而後是零個或更多的中間操做,例如Stream.filter或者 Stream.map
還有一個終端操做,Stream.forEach or Stream.reduce
中間操做返回一條新流,他們老是惰性的;
執行諸如filter()之類的中間操做實際上並不會當即執行任何過濾操做,而是建立了一個新流,當遍歷時,它包含與給定謂詞相匹配的初始流的元素。直到管道的終端操做被執行,管道源的遍歷纔會開始
終端操做,例如Stream.forEach 和 IntStream.sum,能夠遍歷流以產生結果或反作用。
在執行終端操做以後,流管道被認爲是被消耗掉的,而且不能再被使用;
若是您須要再次遍歷相同的數據源,您必須返回到數據源以得到一條新的stream。
在幾乎全部狀況下,終端操做都很迫切,在返回以前完成了數據源的遍歷和管道的處理。只有終端操做iterator() 和 spliterator() 不是;這些都是做爲一個「逃生艙口」提供的,以便在現有操做不足以完成任務的狀況下啓用任意客戶控制的管道遍歷(我的理解就是若是流不足以提供處理可讓你自行遍歷處理)
延遲處理流能夠顯著提升效率;
在像上面的filer-map-sum例子這樣的管道中,過濾、映射和求和能夠被融合到數據的單個傳遞中,而且具備最小的中間狀態。
惰性還容許在沒有必要的狀況下避免檢查全部數據;對於諸如「查找第一個超過1000個字符的字符串」這樣的操做,只須要檢查足夠的字符串,就能夠找到具備所需特徵的字符串,而不須要檢查源的全部字符串。(當輸入流是無限的而不只僅是大的時候,這種行爲就變得更加劇要了。)
中間操做被進一步劃分爲無狀態和有狀態操做。
無狀態操做,如filter和map,在處理新元素時不保留之前處理的元素的狀態——每一個元素均可以獨立於其餘元素的操做處理。有狀態的操做,例如distinct和sorted,能夠在處理新元素時從先前看處處理的元素中合併狀態。
有狀態操做可能須要在產生結果以前處理整個輸入。
例如,一我的不能從排序流中產生任何結果,直到一我的看到了流的全部元素。
所以,在並行計算下,一些包含有狀態中間操做的管道可能須要對數據進行屢次傳遞,或者可能須要緩衝重要數據。包含徹底無狀態的中間操做的管道能夠在單次傳遞過程當中進行處理,不管是順序的仍是並行的,只有最少的數據緩衝
此外,一些操做被認爲是短路操做。一箇中間操做,若是在提供無限流輸入時,它可能會產生一個有限的流,那麼他就是短路的。若是在無限流做爲輸入時,它可能在有限的時間內終止,這個終端操做是短路的。
在管道中進行短路操做是處理無限流在有限時間內正常終止的必要條件,但不是充分條件
|
這些流的方法是如何實現的? 類StreamSupport提供了許多用於建立流的低級方法,全部這些方法都使用某種形式的Spliterator. 一個Spliterator.是迭代器Iterator的並行版本 它描述了一個(多是無限的)元素集合,支持順序前進、批量遍歷,並將一部分輸入分割成另外一個可並行處理的Spliterator 在最低層,全部的流都由一個spliterator 構造(因此說流就是迭代器的一種高級形式) 在實現Spliterator時,有許多實現選擇,幾乎全部的實現都是在簡單的實現和使用Spliterator流的運行時性能之間進行權衡。建立Spliterator的最簡單、但最不高性能的方法是,使用Spliterators.spliteratorUnknownSize(java.util.Iterator, int).雖然這樣的Spliterator能夠工做,但它極可能提供糟糕的並行性能,由於咱們已經丟失了尺寸信息(底層數據集有多大),而且被限制爲一個簡單的分割算法。 一個高質量的Spliterator將提供平衡的和知道大小的分割,精確的尺寸信息,以及一些可用於實現優化執行的spliterator 或數據的 characteristics (見Spliterator int characteristics() ) 可變數據源的Spliterators有一個額外的挑戰; 綁定到數據的時間,由於數據可能在建立spliterator的時間和執行流管道的時間之間發生變化。理想狀況下,一個流的spliterator 應該報告一個characteristic of IMMUTABLE or CONCURRENT; 若是不是,應該是後期綁定。若是一個源不能直接提供一個推薦的spliterator,它可能會間接地經過Supplier提供一個spliterator,經過接收Supplier做爲參數的stream方法構建一個流 public static <T> Stream<T> stream(Supplier<? extends Spliterator<T>> supplier, int characteristics, boolean parallel)
只有在流管道的終端操做開始後,才從supplier處獲
|
支持順序和並行聚合操做的一組元素序列 除了Stream 還有專門爲原始類型特殊化的IntStream、LongStream和double Stream 全部這些都被稱爲「流」
集合和流,雖然表面上有一些類似性,但有不一樣的設計目的
集合主要關注的是對其元素的有效管理和訪問
相比之下,流並無提供直接訪問或操縱其元素的方法,而是關注於聲明性地描述它們的源和計算操做,這些操做將在該源上進行聚合。
可是,若是所提供的流操做沒有提供所需的功能,那麼 BaseStream.iterator() 和 BaseStream.spliterator() 操做能夠用來執行受控的遍歷
示例:
widgets 是 Collection<Widget> int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum();
像上面的「widgets」示例同樣,流管道能夠看做是在流的數據源上進行的查詢。
除非源代碼是爲併發修改而顯式設計的(例如ConcurrentHashMap),不然在查詢時 修改流的源 可能致使不可預測或錯誤的行爲。
大多數流操做都接受描述用戶指定行爲的參數,好比在上面的例子中傳遞給mapToInt的lambda表達式w-w.getweight()。
爲了保持正確的行爲,這些行爲參數:
必須是非干擾(也就是它們不修改流源);
在大多數狀況下,必須是無狀態的(它們的結果不該該依賴於任何在流水線執行過程當中可能發生變化的狀態)
這些參數一般是函數接口的實例,例如Function,通常是lambda表達式或方法引用。除非另有說明,這些參數必須是非空的。
一個流應該只運行一次(調用中間操做或結束操做)。這就排除了好比「forked」流,在這些流中,相同的源提供兩個或更多的管道,或者同一流的多個遍歷。
一個流實現可能會拋出IllegalStateException 異常,若是它檢測到流正在被重用。
然而,因爲某些流操做可能返回它們的接收者而不是一個新的stream對象,因此並不能在全部狀況下都檢測到重用。
Streams有一個BaseStream.close()方法並實現AutoCloseable,可是幾乎全部的stream實例在使用後實際上並不須要關閉。
一般,只有源是IO通道的流(比Files.lines(Path,Charset))將須要關閉。
大多數流都是由集合、數組或生成函數支持的,這些功能不須要特殊的資源管理。(若是流確實須要關閉,它能夠在try-with-resources語句中聲明爲資源。)
流管道能夠按順序或並行執行 ,這種執行模式是流的屬性。
流的類型是建立初始時選擇經過順序或並行操做執行來決定的。(例如,Collections.stream()建立了一個順序流,而Collection.parallelStream()建立了一個並行的流。)
這種執行模式的選擇能夠由BaseStream.sequential() 或BaseStream.parallel()方法修改,而且可使用BaseStream.isParallel() 方法查詢。
|
集合是對一組特定類型的元素值序列提供的接口 是數據結構,提供了元素的存取
流也是對一組特定類型元素值序列提供的接口,在於計算,提供了對元素序列的操做計算方式 好比 filter map等
|
流只能運行一次 |
流由源 0個或者多箇中間操做以及結束操做組成 |
流操做的方法基本上是函數式接口的實例 |
流的中間操做是惰性的並不會當即執行 這更有利於內部迭代的優化 |
流藉助於它內部迭代特性提供了聲明式的編程方式 更急簡潔 |
中間操做自己會返回一個流,能夠將多個操做複合疊加,造成一個更大的流水線 |
流分爲順序和並行兩種方式 |
能夠把流當作一個高級的迭代器Iterator ,內部有它自身運行邏輯的迭代器
你只須要告訴他你想要作什麼,他本身就會自動的去迭代篩選組織你想要的數據
|
Collection stream 集合轉換爲Stream 特 別 注 意:這是一個default方法,也就意味着若是沒有特別處理,全部Collection子類都具備 |
Arrays.stream(); 數組轉換爲Stream |
Stream.iterate Stream類靜態方法 迭代器的形式,建立一個數據流 |
Stream.generate |
BaseStream規定了流的基本接口
Stream中定義了map、filter、flatmap等用戶關注的經常使用操做;
Int~ Long~ Double~是針對於基本類型的特化 方法與Stream中大體對應,固然也有一些差異
BaseStream Stream IntStream LongStream DoubleStream 組建了Java的流體系根基
他們都只是接口
|
PipelineHelper主要用於Stream執行過程當中相關結構的構建 ReferencePipeline 和 AbstractPipeline 完成了Stream的主要實現 AbstractPipeline類實現了全部的 Stream的中間操做和最終操做 [Int | Long | Double] pipeline 同理相似ReferencePipeline只不過是針對基本類型 |
Head、StatelessOp、StatefulOp爲ReferencePipeline中的內部類 [Int | Long | Double]Pipeline 內部也都是定義了這三個內部類 |
你會發現流的生成轉換建立都是使用StreamSupport
StreamSupport 是用於建立和操縱Stream的低級工具方法
除了構造方法,每一個方法都是返回他們對應的Head
ReferencePipeline.Head / DoublePipeline.Head / IntPipeline.Head / LongPipeline.Head
|
數據源 |
操做( filter map.....) |
回調方法(Lambda匿名函數 方法引用) |
Source stage of a ReferencePipeline.Base class for a stateful intermediate stage of a Stream.Base class for a stateless intermediate stage of a Stream.
說白了也就是上面說到過的Head StatefulOp StatelessOp 他們自己也是AbstractPipeline類型的 |
private final AbstractPipeline sourceStage; //反向連接管道鏈的head,也就是說每一個管道節點都有一個頭
private final AbstractPipeline previousStage;//指向上一個
private AbstractPipeline nextStage;//指向下一個
赤裸裸的雙向鏈表
|
Stream中將操做抽象化爲stage 每一個stage 也就是一個AbstractPipeline
每一個stage 至關於一個雙向鏈表的節點 ,每一個節點都保存Head而後保存着上一個和下一個
這個雙向鏈表就構成了整個流水線
(上面的圖看起來next一直是null 是在每一個處理後的this裏面的previousStage裏面 上一個的next是當前)
|
Stream體系是一組接口家族,AbstractPipeline 是接口的實現,PipelineHelper 是管道的輔助類,StreamSupport是流的低級工具類
使用stage來抽象流水線上的每一個操做
其實每一個stage就是一個stream 也就是AbstractPipeline幾個子類的 內部子類 Head StatelessOp statefulOp
StreamSupport用於建立生成Stream 對應的是Head類
其餘的中間操做分爲有狀態和無狀態的,中間操做經過方法好比 filter map 等返回的是StatelessOp 或者 statefulOp
多個stage組合稱爲雙向鏈表的形式 從而成了整個流水線
有了流水線,相鄰兩個操做階段之間如何協調運算?
因而又有了sink的概念,又來協調相鄰的stage之間計算運行
他的模式是begin accept end 還有短路標記
他的accept就是封裝了回調方法
因此說每一個操做stage, StatelessOp 或者 statefulOp中又封裝了Sink
經過AbstractPipeline提供的opWrapSink方法能夠獲取這個sink
調用這個sink的accept方法就能夠調用當前操做的方法
那麼如何串聯起來呢?關鍵點在於opWrapSink方法 ,他接收一個Sink做爲參數
在調用accept方法中 能夠調用這個入參sink的accept方法
這樣子從當前就能調用下一個,也就是說有了推進的動做
那麼只須要找到開始,每一個處理了以後都推進下一個,就順序完成了所欲的操做了
注意上面說的操做都是中間操做,中間操做纔會產生操做階段 終端操做不會增長stage的個數了
|
中間操做 | filter() flatMap() limit() map() concat() distinct() peek() skip() sorted() parallel() sequential() unordered() flatMapTo[Double | Int | Long] mapTo[ Double | Int | Long ] |
結束操做 | allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
流到流之間的轉換 filter(過濾), map(映射轉換), mapTo[Int|Long|Double] (到基本類型流的轉換), flatMap(流展開合併),flatMapTo[Int|Long|Double], sorted(排序),distinct(不重複值),peek(執行某種操做,流不變,可用於調試), limit(限制到指定元素數量), skip(跳過若干元素) |
流到終值的轉換 toArray(轉爲數組),reduce(推導結果),collect(聚合結果), min(最小值), max(最大值), count (元素個數), anyMatch (任一匹配), allMatch(全部都匹配), noneMatch(一個都不匹配) findFirst(選擇首元素),findAny(任選一元素) |
直接遍歷 forEach (不保證順序遍歷,好比並行流), forEachOrdered(順序遍歷) |
構造流 empty (構造空流),of (單個元素的流及多元素順序流) iterate (無限長度的有序順序流),generate (將數據提供器轉換成無限非有序的順序流), concat (流的鏈接), Builder (用於構造流的Builder對象) |
filter 條件篩選 boolean test(T t); |
map 數據轉換 R apply(T t); 新的值替換Stream中的值 mapTo[Int | Long | Double] 相似 |
生成流 iterate()
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
|
Stream.iterate(0, i -> i + 2)
java
生成流 generate() public static<T> Stream<T> generate(Supplier<T> s) |
一個歸約操做(也稱爲摺疊)接受一系列的輸入元素,並經過重複應用組合操做將它們組合成一個簡單的結果
例如查找一組數字的總和或最大值,或者將元素累積到一個列表中。
流的類中有多種形式的通用歸約操做,稱爲reduce()和collect(),以及多個專門化的簡化形式,如sum()、max()或count()。
固然,這樣的操做能夠很容易地實現爲簡單的順序循環,以下所示:
int sum = 0; for (int x : numbers) { sum += x; }
然而,咱們有充分的理由傾向於減小操做,而不是像上面這樣的累加運算。
它不只是一個「更抽象的」——它在流上做爲一個總體而不是單獨的元素來運行——並且一個適當構造的reduce操做本質上是可並行的,只要用於處理元素的函數(s)是結合的和無狀態的。舉個例子,給定一個數字流,咱們想要找到和,咱們能夠寫:
int sum = numbers.stream().reduce(0, (x,y) -> x+y);
或者
int sum = numbers.stream().reduce(0, Integer::sum);
這些歸約操做幾乎不須要修改就能夠並行運行
int sum = numbers.parallelStream().reduce(0, Integer::sum);
若是一個操做符或函數 op 知足 (a op b) op c == a op (b op c) ,那麼他是結合的
結合性對於並行結算很是重要
好比
a op b op c op d == (a op b) op (c op d) 就能夠並行計算 (a op b) (c op d) 而後再處理他們
|