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. 操作是如何記錄下來的?
- Head記錄Stream起始操作
- StatelessOp記錄中間操作
- 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