1. 程式人生 > >Java8學習記錄(二)-Stream原理

Java8學習記錄(二)-Stream原理

推薦一篇博文,很好的介紹了Stream的原理.本文對其進行一些補充更加詳細的講解.

需求:

"張三","李四","王二","張四五"中選出以開頭的名字,然後從再從中選出名字最長的一個,輸出其長度.

1.一種直白的實現

缺點:
1. 迭代次數過多
2. 頻繁產生中間結果,效能無法接受

實際想要的效果:
平常的寫法:

int longest = 0;
for(String str : strings){
    if(str.startsWith("張")){// 1. filter(), 保留以張開頭的字串
        int len = str.length();// 2. mapToInt(), 轉換成長度
longest = Math.max(len, longest);// 3. max(), 保留最長的長度 } } System.out.println(longest);

Stream的做法:

  Stream.of("張三","李四","王二","張四五")
        .filter(x -> x.startsWith("張"))
        .mapToInt(String::length)
        .max()
        .ifPresent(System.out::println);

2.Stream是怎麼做到的?

Stream的操作分類

:

中間操作:返回一個新的Stream
- 有狀態 sorted(),必須等上一步操作完拿到全部元素後才可操作
- 無狀態 filter(),該操作的元素不受上一步操作的影響

  list.stream().filter(x -> x.startWith("張").map(x -> x.length())
  list.stream().filter(x -> x.startWith("張").sorted().map(x -> x.length())

終端操作:返回結果
- 短路操作findFirst(),找到一個則返回,也就是break當前的迴圈
- 非短路操作forEach(),遍歷全部元素

以上操作決定了Stream一定是先構建完畢再執行的特點,也就是延遲執行,當需要結果(終端操作時)開始執行流水線.
Stream做到的是對於多次呼叫合併到一次迭代中處理完所有的呼叫方式.換句話說就是解決了上述的兩個缺點.大概思路是記錄下每一步的操作,然後終端操作時對其迭代依次執行每一步的操作,最後再一次迴圈中處理.

問題:
1. 操作是如何記錄下來的?
2. 操作是如何疊加的?
3. 疊加完如何執行的?
4. 執行完如何收集結果的?

Stream結構示意圖:

示例程式碼:

    List<String> data = new ArrayList<>();
    data.add("張三");
    data.add("李四");
    data.add("王三");
    data.add("馬六");

    data.stream()
        .filter(x -> x.length() == 2)
        .map(x -> x.replace("三","五"))
        .sorted()
        .filter(x -> x.contains("五"))
        .forEach(System.out::println);

1. 操作是如何記錄下來的?

  1. Head記錄Stream起始操作
  2. StatelessOp記錄中間操作
  3. StatefulOp記錄有狀態的中間操作
    這三個操作例項化會指向其父類AbstractPipeline,也就是在AbstractPipeline中建立了雙向連結串列

對於Head

    AbstractPipeline(Spliterator<?> source,
                     int sourceFlags, boolean parallel) {
        this.previousStage = null; //首操作上一步為null    
        this.sourceSpliterator = source; //資料
        this.sourceStage = this; //Head操作
        this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
        this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
        this.depth = 0;
        this.parallel = parallel;
    }

對於其他Stage:

    AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
        if (previousStage.linkedOrConsumed)
            throw new IllegalStateException(MSG_STREAM_LINKED);
        previousStage.linkedOrConsumed = true;
        //雙向連結串列的建立
        previousStage.nextStage = this;
        this.previousStage = previousStage;
        this.sourceStage = previousStage.sourceStage;        
        this.depth = previousStage.depth + 1;        

        this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
        this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
        if (opIsStateful())
            sourceStage.sourceAnyStateful = true;
    }


呼叫過程如此用雙向連結串列串聯起來,每一步都得知其上一步與下一步的操作.
data.stream()
.filter(x -> x.length() == 2)
.map(x -> x.replace(“三”,”五”))
.sorted()
.filter(x -> x.contains(“五”))
.forEach(System.out::println);

2.操作是如何疊加的?

Sink<T>介面:
1. void begin(long size),迴圈開始前呼叫,通知每個Stage做好準備
2. void end(),迴圈結束時呼叫,依次呼叫每個Stage的end方法,處理結果
3. boolean cancellationRequested(),判斷是否可以提前結束迴圈
4. void accept(T value),每一步的處理

其子類之一ChainedReference:

    static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
        protected final Sink<? super E_OUT> downstream;

        public ChainedReference(Sink<? super E_OUT> downstream) {
            this.downstream = Objects.requireNonNull(downstream);
        }
        @Override
        public void begin(long size) {
            downstream.begin(size);
        }
        @Override
        public void end() {
            downstream.end();
        }
        @Override
        public boolean cancellationRequested() {
            return downstream.cancellationRequested();
        }
    }

例Filter:

    @Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
                return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(P_OUT u) {
                        //條件成立則傳遞給下一個操作,也因為如此所以有狀態的操作必須放到
                        //end方法裡面
                        if (predicate.test(u))
                            downstream.accept(u);
                    }
                };
            }
        };
    }

再例如sorted():

        @Override
        public void begin(long size) {
            if (size >= Nodes.MAX_ARRAY_SIZE)
                throw new IllegalArgumentException(Nodes.BAD_SIZE);
            list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
        }
        @Override
        public void end() {
            list.sort(comparator);
            downstream.begin(list.size());
            if (!cancellationWasRequested) {
                list.forEach(downstream::accept);
            }
            else {
                for (T t : list) {
                    if (downstream.cancellationRequested()) break;
                    downstream.accept(t);
                }
            }
            downstream.end();
            list = null;
        }
        @Override
        public void accept(T t) {
            list.add(t);
        }

疊加後如何執行?

執行操作是由終端操作來觸發的,例如foreach操作

    @Override
    public void forEach(Consumer<? super P_OUT> action) {
        //evaluate就是開關,一旦呼叫就立即執行整個Stream    
        evaluate(ForEachOps.makeRef(action, false));
    }

執行前會對操作從末尾到起始反向包裹起來,得到呼叫鏈

Sink opWrapSink(int flags, Sink<P_OUT> sink) ;
    //這個Sink是終端操作所對應的Sink
    final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
        Objects.requireNonNull(sink);

        for ( AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
            sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
        }
        return (Sink<P_IN>) sink;
    }

    @Override
    final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
        Objects.requireNonNull(wrappedSink);

        if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
            //依次執行呼叫鏈
            wrappedSink.begin(spliterator.getExactSizeIfKnown());
            spliterator.forEachRemaining(wrappedSink);
            wrappedSink.end();
        }
        else {
            copyIntoWithCancel(wrappedSink, spliterator);
        }
    }

有狀態的中間操作何時執行?

例如sorted()操作,其依賴上一次操作的結果集,按照呼叫鏈來說結果集必須在accept()呼叫完才會產生.那也就說明sorted操作需要在end中,然後再重新開啟呼叫鏈.

sorted的end方法:

       @Override
        public void end() {
            list.sort(comparator);
            downstream.begin(list.size());
            if (!cancellationWasRequested) {
                list.forEach(downstream::accept);
            }
            else {
                for (T t : list) {
                    if (downstream.cancellationRequested()) break;
                    downstream.accept(t);
                }
            }
            downstream.end();
            list = null;
        }

那麼就相當於sorted給原有操作斷路了一次,然後又重新接上,再次遍歷.

如何收集到結果?

foreach是不需要收集到結果的,但是對於collect這樣的操作是需要拿到最終end產生的結果.end產生的結果在最後一個Sink中,這樣的操作最終都會提供一個取出資料的get方法.

       @Override
        public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
                                           Spliterator<P_IN> spliterator) {
            return helper.wrapAndCopyInto(makeSink(), spliterator).get();
        }

如此拿到資料返回

相關推薦

Java8學習記錄()-Stream原理

推薦一篇博文,很好的介紹了Stream的原理.本文對其進行一些補充更加詳細的講解. 需求: 從"張三","李四","王二","張四五"中選出以張開頭的名字,然後從再從中選出名字最長的一個,輸出其長度. 1.一種直白的實現 缺點: 1

vue.js 學習記錄()

分離 理解 foo 選項 這也 lsp 生效 html標簽 tro 原文連接:http://www.cnblogs.com/keepfool/p/5625583.html 組件 #註冊組件 Vue.component(‘my-component‘, { // 選項

React 學習記錄()

p s react index tdi 運算 color dom 字母 tor JSX語法 jsx是應React的出現而出現的,由js和xml結合而成,遇"<"解析xml,遇"{"解析js,利用js來虛擬DOM,利用虛擬DOM Diff算法可以讓用戶毫

ELK學習記錄 :elasticsearch、logstash及kibana的安裝與配置

jre_home 支持 number yml num des 安裝包 soft filters 註意事項: 1.ELK版本要求5.X以上,本人使用版本:elasticsearch-6.0.0、kibana-6.0.0-linux-x86_64、logstash-6.0.0.

easylogging++學習記錄():流式日誌

析構 log middle 方式 pat eas _id stream 流式 easylogging++日誌庫流式日誌的寫入,依賴於el::base::Writer類的析構,以debug日誌為例:具體代碼如下: #define LOG(LEVEL) CLOG(LEVEL,

Linux學習記錄()

image 關於 安裝 gbk 方式 inux 統一 窗口 都在 1、遠程連接工具的使用 實際開發中,Linux服務器都在其他的地方,我們要通過遠程的方式去連接Linux並操作它,Linux遠程的操作工具有很多,企業中常用的有Puttty、secureCRT、SSH Sec

jQuery學習記錄

索引選擇器 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> <script src="

Java學習記錄

  ①關鍵字:•被Java語言賦予特定含義的單詞,常用的關鍵字不需要記憶,用的多了自然就熟了。      關鍵字特點           •組成關鍵字的字母全部小

Docker學習記錄 -- Dokcer安裝Ubuntu容器後,命令無法執行問題

我的理解為,通過映象安裝的ubuntu容器為純淨的環境,其好多命令執行不了,即缺少很多可執行指令碼,安裝即可~~~ 執行一切安裝之前請先執行 apt-get update 1. lsb_release apt-get install lsb-release 2. ifconfig ap

git學習記錄

一.版本回退 [email protected]:~/learngit$ git log commit 8e749cbd2e6be927c4aa6ffcd8e56df142457654 Author: duankun <[email protected]> Dat

springcloud 學習記錄 開源架構 PIG

碼雲上找了個最近比較火的開源架構 PIG 搭建了一天,終於大功告成,可以慢慢研究學習,再發上微博。 下面介紹一下PIG 基於Spring Cloud、OAuth2.0、Vue的前後端分離的系統。 通用RBAC許可權設計及其資料許可權和分庫分表 支援服務限流、動態路由

Java8學習筆記之Stream API

Stream是Java8引入的一個重度使用lambda表示式的API。 Stream可以用流的方式處理資料集合,在Java8之前,我們處理這些集合,是需要迭代器的,比如iterator,這是外部迭代;而Stream是內部迭代,我們不用關心集合內部元素是如何

JPA學習記錄(搭建一個JPA+hibernate例項)

一:首先開發JPA依賴的jar檔案        二:JPA的配置檔案          JPA規範要求在類路徑的META-INF目錄下放置persistence.xml,檔案的名稱是固定的 <persistence xmlns="http://java.sun.c

C++學習記錄

強制型別轉化 C中:char *ptr;                    C++中:double num = 5;          int *p = (int *)ptr;                      int count =static_cast<

Java SE學習記錄

Java的資料型別分為基本資料型別和引用資料型別,其中基本資料型別包括:整型,浮點型,字元型和布林型,而引用資料型別包括:陣列,類和介面。 作為一個接觸最早的引用資料型別,陣列一組相同資料型別的組合 陣列是Java中一個非常重要的概念,在實際開發中,陣列

Spring Boot學習記錄()--thymeleaf模板

Spring Boot學習記錄(二)–thymeleaf模板 標籤(空格分隔): spring-boot 自從來公司後都沒用過jsp當介面渲染了,因為前後端分離不是很好,反而模板引擎用的比較多,thymeleaf最大的優勢字尾為html,就是隻需要瀏覽器

Java8學習記錄(三)-強大的collect操作

collect應該說是Stream中最強大的終端操作了,使用其幾乎能得到你想要的任意資料的聚合,下面好好分析該工具的用法. 在Stream介面中有如下兩個方法 <R> R collect(Supplier<R> suppl

springBoot 學習記錄()-返回json資料的幾種方式

一:新建maven專案 pom.xml 程式碼: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www

elasticsearch6.3.2學習記錄 《spring boot 搭建es開發環境,建立索引,新增資料,查詢檢索》

spring boot + maven + idea jdk1.8以上 搭建 一、 pom.xml檔案 ,如果不需要連線資料庫,可以不引入資料庫連線依賴,在程式入口類加上這句註解 @EnableAutoConfiguration(exclud

StoryBoard學習記錄:關於UIStoryboardSegue

1: 使用UIStoryboardSegue跳轉時觸發 此方法優先與下一個ViewController 的 viewDidLoad 方法 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sende