Java8學(xué)習(xí)筆記之并行數(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)行同步來(lái)避免不希望出現(xiàn)的競(jìng)爭(zhēng)條件,等待所有線程完成憨降,最后把這些部分結(jié)果合并起來(lái)螟深。Java 7引入了一個(gè)叫作分支/合并的框架,讓這些操作更穩(wěn)定谊迄、更不易出錯(cuò)。在Java 8中可以使用Stream接口更容易的對(duì)數(shù)據(jù)集執(zhí)行并行操作。

1荚虚、并行流

通過(guò)對(duì)收集源調(diào)用parallelStream方法來(lái)把集合轉(zhuǎn)換為并行流。并行流就是一個(gè)把內(nèi)容分成多個(gè)數(shù)據(jù)塊籍茧,并用不同的線程分別處理每個(gè)數(shù)據(jù)塊的流版述。

示例:返回從1到給定參數(shù)的所有數(shù)字的和

public static long getSum(long n) {

????return Stream.iterate(1L, i -> i + 1) //生成自然數(shù)無(wú)限流

????????.limit(n) //限制到前n個(gè)數(shù)

????????.reduce(0L, Long::sum); //對(duì)所有數(shù)字求和來(lái)歸納流

}

上面代碼等價(jià)于:

public static long getSum(long n) {?

????long result = 0;?

? ? for (long i = 1L; i <= n; i++) { result += i; }?

? ? return result;

}

將順序流轉(zhuǎn)換為并行流

對(duì)順序流調(diào)用parallel方法,轉(zhuǎn)換為并行流:

public static long parallelGetSum(long n) {

????return Stream.iterate(1L, i -> i + 1)

????????.limit(n)

????????.parallel() //將流轉(zhuǎn)換為并行流

????????.reduce(0L, Long::sum);

}

并行歸納操作

注意:對(duì)順序流調(diào)用parallel方法并不意味著流本身有任何實(shí)際的變化寞冯。它在內(nèi)部實(shí)際上就是設(shè)了一個(gè)boolean標(biāo)志渴析,表示你想讓調(diào)用parallel之后進(jìn)行的所有操作都并行執(zhí)行。你只需要對(duì)并行流調(diào)用sequential方法就可以把它變成順序流吮龄。你可能以為把這兩個(gè)方法結(jié)合起來(lái)俭茧,就可以更細(xì)化地控制在遍歷流時(shí)哪些操作要并行執(zhí)行,哪些要順序執(zhí)行漓帚。例如恢恼,你可以這樣做:

stream.parallel()

????.filter(...)

????.sequential()

????.map(...)

????.parallel()

????.reduce();

實(shí)際上最后一次parallel或sequential的調(diào)用會(huì)影響整個(gè)流水線。

并行流內(nèi)部使用了默認(rèn)的ForkJoinPool胰默,它默認(rèn)的線程數(shù)量就是你的處理器數(shù)量场斑,這個(gè)值是由Runtime.getRuntime().availableProcessors()得到的。你可以通過(guò)系統(tǒng)屬性java.util.concurrent.ForkJoinPool.common.parallelism來(lái)改變線程池大小牵署,如下所示:System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");

這是一個(gè)全局設(shè)置漏隐,它將影響代碼中所有的并行流,目前還無(wú)法專為某個(gè)并行流指定這個(gè)值奴迅。一般情況下建議你不要修改此值青责,除非有充足的理由。

測(cè)量流性能

測(cè)量對(duì)前n個(gè)自然數(shù)求和的函數(shù)的性能:

public long measureSumPerf(Function<Long, Long> adder, long n) {

????long fastest = Long.MAX_VALUE;

????for (int i = 0; i < 10; i++) {

????????long start = System.nanoTime();

????????long sum = adder.apply(n);

????????long duration = (System.nanoTime() - start) / 1_000_000;

????????System.out.println("Result: " + sum);

????????if (duration < fastest) fastest = duration;

????}

????return fastest;

}

System.out.println("Sequential sum done in:" +? measureSumPerf(ParallelStreams::getSum, 10_000_000) + " msecs"); //測(cè)試順序加法器函數(shù)對(duì)前一千萬(wàn)個(gè)自然數(shù)求和耗時(shí)

System.out.println("Parallel sum done in: " + measureSumPerf(ParallelStreams::parallelGetSum, 10_000_000) + " msecs" );//測(cè)試并行加法器函數(shù)對(duì)前一千萬(wàn)個(gè)自然數(shù)求和耗時(shí)

注意:這個(gè)運(yùn)行結(jié)果會(huì)存在差異性取具,因?yàn)橛绊憟?zhí)行時(shí)間的因素有很多脖隶,比如你的電腦支持多少個(gè)內(nèi)核。用傳統(tǒng)for循環(huán)的迭代版本執(zhí)行起來(lái)應(yīng)該會(huì)快很多暇检,因?yàn)樗鼮榈讓硬澹匾氖遣恍枰獙?duì)原始類型做任何裝箱或拆箱操作。

實(shí)際執(zhí)行結(jié)果是并行版本比順序版本要慢很多块仆,原因是:iterate生成的是裝箱的對(duì)象构蹬,必須拆箱成數(shù)字才能求和王暗;iterate很難分割成能夠獨(dú)立執(zhí)行的小塊,因?yàn)槊看螒?yīng)用這個(gè)函數(shù)都要依賴前一次應(yīng)用的結(jié)果庄敛。把流標(biāo)記成并行俗壹,其實(shí)是給順序處理增加了開銷,它還要把每次求和操作分到一個(gè)不同的線程上藻烤。

如何利用多核處理器绷雏,用流來(lái)高效地執(zhí)行并行?

可以使用LongStream.rangeClosed的方法怖亭,相比iterate于其優(yōu)點(diǎn)是:直接產(chǎn)生原始類型的long數(shù)字涎显,沒有裝箱拆箱的開銷。會(huì)生成數(shù)字范圍依许,很容易拆分為獨(dú)立的小塊棺禾。

測(cè)試代碼:

public static long rangedSum(long n) {

????return LongStream.rangeClosed(1, n).reduce(0L, Long::sum);

}

public static long parallelRangedSum(long n) {

????return LongStream.rangeClosed(1, n).parallel().reduce(0L, Long::sum);

}

測(cè)試方法:

System.out.println("Parallel range sum done in:" +? ? measureSumPerf(ParallelStreams::parallelRangedSum, 10_000_000) + " msecs");

結(jié)果是得到了一個(gè)比順序執(zhí)行更快的并行歸納。表明使用正確的數(shù)據(jù)結(jié)構(gòu)然后使其并行工作能夠保證最佳的性能峭跳。

注意:并行化并不是沒有代價(jià)的膘婶。并行化過(guò)程本身需要對(duì)流做遞歸劃分,把每個(gè)子流的歸納操作分配到不同的線程蛀醉,然后把這些操作的結(jié)果合并成一個(gè)值悬襟。但在多個(gè)內(nèi)核之間移動(dòng)數(shù)據(jù)的代價(jià)也可能比你想的要大,所以很重要的一點(diǎn)是要保證在內(nèi)核中并行執(zhí)行工作的時(shí)間比在內(nèi)核之間傳輸數(shù)據(jù)的時(shí)間長(zhǎng)拯刁〖乖溃總而言之,很多情況下不可能或不方便并行化垛玻。在使用并行Stream加速代碼之前割捅,你必須確保用得對(duì);

流的數(shù)據(jù)源和可分解性

2帚桩、分支/合并框架

分支/合并框架的目的是以遞歸方式將可以并行的任務(wù)拆分成更小的任務(wù)亿驾,然后將每個(gè)子任務(wù)的結(jié)果合并起來(lái)生成整體結(jié)果。它是ExecutorService接口的一個(gè)實(shí)現(xiàn)账嚎,它把子任務(wù)分配給線程池(稱為ForkJoinPool)中的工作線程莫瞬。

1)使用RecursiveTask

要把任務(wù)提交到這個(gè)池,必須創(chuàng)建RecursiveTask<R>的一個(gè)子類郭蕉,其中R是并行化任務(wù)(以及所有子任務(wù))產(chǎn)生的結(jié)果類型疼邀,或者如果任務(wù)不返回結(jié)果,則是RecursiveAction類型(它可能會(huì)更新其他非局部機(jī)構(gòu))召锈。要定義RecursiveTask旁振,只需實(shí)現(xiàn)它唯一的抽象方法compute:

protected abstract R compute();

這個(gè)方法同時(shí)定義了將任務(wù)拆分成子任務(wù)的邏輯,以及無(wú)法再拆分或不方便再拆分時(shí),生成單個(gè)子任務(wù)結(jié)果的邏輯规求。

遞歸的任務(wù)拆分過(guò)程

運(yùn)行ForkJoinSumCalculator

當(dāng)把ForkJoinSumCalculator任務(wù)傳給ForkJoinPool時(shí)筐付,這個(gè)任務(wù)就由池中的一個(gè)線程執(zhí)行卵惦,這個(gè)線程會(huì)調(diào)用任務(wù)的compute方法阻肿。該方法會(huì)檢查任務(wù)是否小到足以順序執(zhí)行,如果不夠小則會(huì)把要求和的數(shù)組分成兩半沮尿,分給兩個(gè)新的ForkJoinSumCalculator丛塌,而它們也由ForkJoinPool安排執(zhí)行。這一過(guò)程可以遞歸重復(fù)畜疾,把原任務(wù)分為更小的任務(wù)赴邻,直到滿足不方便或不可能再進(jìn)一步拆分的條件。這時(shí)會(huì)順序計(jì)算每個(gè)任務(wù)的結(jié)果啡捶,然后由分支過(guò)程創(chuàng)建的(隱含的)任務(wù)二叉樹遍歷回到它的根姥敛。接下來(lái)會(huì)合并每個(gè)子任務(wù)的部分結(jié)果,從而得到總?cè)蝿?wù)的結(jié)果瞎暑。

分支/合并算法

2)使用分支/合并框架的最佳做法

-對(duì)一個(gè)任務(wù)調(diào)用join方法會(huì)阻塞調(diào)用方彤敛,直到該任務(wù)做出結(jié)果。因此了赌,有必要在兩個(gè)子任務(wù)的計(jì)算都開始之后再調(diào)用它墨榄。否則,你得到的版本會(huì)比原始的順序算法更慢更復(fù)雜勿她,因?yàn)槊總€(gè)子任務(wù)都必須等待另一個(gè)子任務(wù)完成才能啟動(dòng)袄秩。

-不應(yīng)該在RecursiveTask內(nèi)部使用ForkJoinPool的invoke方法。相反逢并,你應(yīng)該始終直接調(diào)用compute或fork方法之剧,只有順序代碼才應(yīng)該用invoke來(lái)啟動(dòng)并行計(jì)算。

-對(duì)子任務(wù)調(diào)用fork方法可以把它排進(jìn)ForkJoinPool砍聊。同時(shí)對(duì)左邊和右邊的子任務(wù)調(diào)用它似乎很自然背稼,但這樣做的效率要比直接對(duì)其中一個(gè)調(diào)用compute低。這樣做你可以為其中一個(gè)子任務(wù)重用同一線程辩恼,從而避免在線程池中多分配一個(gè)任務(wù)造成的開銷雇庙。

-調(diào)試使用分支/合并框架的并行計(jì)算可能有點(diǎn)棘手。特別是你平常都在IDE里面看棧跟蹤(stack trace)來(lái)找問(wèn)題灶伊,但放在分支-合并計(jì)算上就不行了疆前,因?yàn)檎{(diào)用compute的線程并不是概念上的調(diào)用方,后者是調(diào)用fork的那個(gè)聘萨。

-和并行流一樣竹椒,不應(yīng)想當(dāng)然地認(rèn)為在多核處理器上使用分支/合并框架就比順序計(jì)算快。一個(gè)任務(wù)可以分解成多個(gè)獨(dú)立的子任務(wù)米辐,才能讓性能在并行化時(shí)有所提升胸完。所有這些子任務(wù)的運(yùn)行時(shí)間都應(yīng)該比分出新任務(wù)所花的時(shí)間長(zhǎng)书释;一個(gè)慣用方法是把輸入/輸出放在一個(gè)子任務(wù)里,計(jì)算放在另一個(gè)里赊窥,這樣計(jì)算就可以和輸入/輸出同時(shí)進(jìn)行爆惧。此外,在比較同一算法的順序和并行版本的性能時(shí)還有別的因素要考慮锨能。就像任何其他Java代碼一樣扯再,分支/合并框架需要“預(yù)熱”或者說(shuō)要執(zhí)行幾遍才會(huì)被JIT編譯器優(yōu)化。這就是為什么在測(cè)量性能之前跑幾遍程序很重要址遇。另外熄阻,編譯器內(nèi)置的優(yōu)化可能會(huì)為順序版本帶來(lái)一些優(yōu)勢(shì)。

-你必須選擇一個(gè)標(biāo)準(zhǔn)倔约,來(lái)決定任務(wù)是要進(jìn)一步拆分還是已小到可以順序求值秃殉。

3)工作竊取

一般來(lái)說(shuō)分出大量的小任務(wù)都是一個(gè)好選擇。在理想情況下浸剩,劃分并行任務(wù)時(shí)钾军,應(yīng)該讓每個(gè)任務(wù)都用完全相同的時(shí)間完成,讓所有的CPU內(nèi)核都同樣繁忙乒省。但實(shí)際中巧颈,每個(gè)子任務(wù)所花的時(shí)間可能天差地別,要么是因?yàn)閯澐植呗孕实托淇福词怯胁豢深A(yù)知的原因砸泛,比如磁盤訪問(wèn)慢,或是需要和外部服務(wù)協(xié)調(diào)執(zhí)行蛆封。

分支/合并框架用一種稱為工作竊却浇浮(work stealing)的技術(shù)來(lái)解決這個(gè)問(wèn)題。在實(shí)際應(yīng)用中惨篱,這些任務(wù)差不多被平均分配到ForkJoinPool中的所有線程上盏筐。每個(gè)線程都為分配給它的任務(wù)保存一個(gè)雙向鏈?zhǔn)疥?duì)列,每完成一個(gè)任務(wù)砸讳,就會(huì)從隊(duì)列頭上取出下一個(gè)任務(wù)開始執(zhí)行琢融。某個(gè)線程可能早早完成了分配給它的所有任務(wù),它的隊(duì)列已經(jīng)空了簿寂,而其他的線程還很忙漾抬。這時(shí),這個(gè)線程并沒有閑下來(lái)常遂,而是隨機(jī)選了一個(gè)別的線程纳令,從隊(duì)列的尾巴上“偷走”一個(gè)任務(wù)。這個(gè)過(guò)程一直繼續(xù)下去,直到所有的任務(wù)都執(zhí)行完畢平绩,所有的隊(duì)列都清空圈匆。這就是為什么要?jiǎng)澇稍S多小任務(wù)而不是少數(shù)幾個(gè)大任務(wù),這有助于更好地在工作線程之間平衡負(fù)載捏雌。 這種工作竊取算法用于在池中的工作線程之間重新分配和平衡任務(wù)跃赚。

分支/合并框架使用的工作竊取算法

3、可分迭代器Spliterator

Spliterator是Java 8中加入的另一個(gè)新接口腹忽,含義是可分迭代器来累。和Iterator一樣砚作,Spliterator也用于遍歷數(shù)據(jù)源中的元素窘奏,但它是為了并行執(zhí)行而設(shè)計(jì)的。Java 8已經(jīng)為集合框架中包含的所有數(shù)據(jù)結(jié)構(gòu)提供了一個(gè) 默認(rèn)的Spliterator實(shí)現(xiàn)葫录。集合實(shí)現(xiàn)了Spliterator接口着裹,接口提供了一個(gè)spliterator方法。

Spliterator接口定義:

public interface Spliterator<T> {

????boolean tryAdvance(Consumer<? super T> action);

????Spliterator<T> trySplit();

????long estimateSize();

????int characteristics();

}

T是Spliterator遍歷的元素的類型米同。

tryAdvance方法的行為類似于普通的Iterator骇扇,它按順序一個(gè)個(gè)使用Spliterator中的元素,如果還有其他元素要遍歷就返回true面粮。

trySplit是專為Spliterator接口設(shè)計(jì)的少孝,它可以把一些元素劃分給第二個(gè)Spliterator(由該方法返回),讓它們兩個(gè)并行處理熬苍。

estimateSize方法可以估計(jì)還剩下多少元素要遍歷稍走,即使不那么確切,能快速算出來(lái)是一個(gè)值 也有助于讓拆分均勻一點(diǎn)柴底。

1)拆分過(guò)程

將Stream拆分成多個(gè)部分的算法是一個(gè)遞歸過(guò)程婿脸。

遞歸拆分過(guò)程

第一步:對(duì)第一個(gè)Spliterator調(diào)用trySplit,生成第二個(gè)Spliterator柄驻。

第二步:對(duì)這兩個(gè)Spliterator調(diào)用trysplit狐树,這樣就有了四個(gè)Spliterator。

第三步:不斷對(duì)Spliterator調(diào)用trySplit直到返回null鸿脓,表明它處理的數(shù)據(jù)結(jié)構(gòu)不能再分割抑钟。

最后:這個(gè)遞歸拆分過(guò)程到第四步就終止了,這時(shí)所有的Spliterator在調(diào)用trySplit時(shí)都返回了null野哭。

Spliterator的特性

Spliterator接口聲明的最后一個(gè)抽象方法是characteristics在塔,它將返回一個(gè)int,代表Spliterator本身特性集的編碼虐拓。

Spliterator的特性

2)實(shí)現(xiàn)自己的Spliterator

開發(fā)一個(gè)簡(jiǎn)單的方法來(lái)計(jì)數(shù)一個(gè)String中的單詞數(shù)心俗。

public int countWordsIteratively(String s) {

????int counter = 0;

????boolean lastSpace = true;

????for (char c : s.toCharArray()) {

????????if (Character.isWhitespace(c)) {

????????????lastSpace = true;

????????} else {

????????????if (lastSpace) counter++; //上一個(gè)字符是空格,而當(dāng)前遍歷的字符不是空格時(shí),將單詞計(jì)數(shù)器加一

????????????lastSpace = false;

????????}

????}

????return counter;

}

以函數(shù)式風(fēng)格重寫單詞計(jì)數(shù)器:

首先需要把String轉(zhuǎn)換成一個(gè)流城榛。但是原始類型的流僅限于int揪利、long和double, 所以只能用Stream<Character>:

Stream<Character> stream = IntStream.range(0, SENTENCE.length()) ????.mapToObj(SENTENCE::charAt);

你可以對(duì)這個(gè)流做歸約來(lái)計(jì)算字?jǐn)?shù)狠持。在歸約流時(shí)疟位,需要保留由兩個(gè)變量組成的狀態(tài):一個(gè)int

用來(lái)計(jì)算到目前為止數(shù)過(guò)的字?jǐn)?shù),還有一個(gè)boolean用來(lái)記得上一個(gè)遇到的Character是不是空 格喘垂。你需要?jiǎng)?chuàng)建一個(gè)新類WordCounter來(lái)把這個(gè)狀態(tài)封裝起來(lái)甜刻。

用來(lái)在遍歷Character流時(shí)計(jì)數(shù)的類:

class WordCounter {

????private final int counter;

????private final boolean lastSpace;

????public WordCounter(int counter, boolean lastSpace) {

????????this.counter = counter;

????????this.lastSpace = lastSpace;

????}

? ? ? public WordCounter accumulate(Character c) {//和迭代算法一樣,accumulate方法一個(gè)個(gè)遍歷Character

????????if (Character.isWhitespace(c)) {

????????????return lastSpace ? this : new WordCounter(counter, true);

????????} else {

????????????return lastSpace ? new WordCounter(counter + 1, false) : this; //上一個(gè)字符是空格正勒,而當(dāng)前遍歷的字符不是空格時(shí)得院,將單詞計(jì)數(shù)器加一

????????}

????}

? ??//合并兩個(gè)WordCounter,把其計(jì)數(shù)器加起來(lái)

? ? public WordCounter combine(WordCounter wordCounter) {?

????????return new WordCounter(counter + wordCounter.counter, wordCounter.lastSpace);//僅需要計(jì)數(shù)器的總和章贞,無(wú)需關(guān)心lastSpace

????}

????public int getCounter() {return counter;}

}

accumulate方法定義了如何更改WordCounter的狀態(tài)祥绞,確切地說(shuō)是用哪個(gè)狀態(tài)來(lái)建立新的WordCounter,因?yàn)檫@個(gè)類是不可變的鸭限。每次遍歷到Stream中的一個(gè)新的Character時(shí)蜕径,就會(huì)調(diào)用accumulate方法。就如countWords- Iteratively方法一樣败京,當(dāng)上一個(gè)字符是空格兜喻,新字符不是空格時(shí),計(jì)數(shù)器就加一赡麦。

調(diào)用第二個(gè)方法combine時(shí)朴皆,會(huì)對(duì)作用于Character流的兩個(gè)不同子部分的兩個(gè)WordCounter的部分結(jié)果進(jìn)行匯總,也就是把兩個(gè)WordCounter內(nèi)部的計(jì)數(shù)器加起來(lái)隧甚。?

遍歷到新的Character c時(shí)WordCounter的狀態(tài)轉(zhuǎn)換

歸約Character流:

private int countWords(Stream<Character> stream) {

????WordCounter wordCounter = stream.reduce(

????????new WordCounter(0, true), WordCounter::accumulate, WordCounter::combine);

????return wordCounter.getCounter();

}

調(diào)用方法:

Stream<Character> stream = IntStream.range(0, SENTENCE.length())

????.mapToObj(SENTENCE::charAt);

System.out.println("Found " + countWords(stream) + " words");

讓W(xué)ordCounter并行工作:

在并行執(zhí)行時(shí)必須要確保String不是在隨機(jī)位置拆開的车荔,而只能在詞尾拆開。需要為Character實(shí)現(xiàn)一個(gè)Spliterator戚扳,它只能在兩個(gè)詞之間拆開String忧便,然后由此創(chuàng)建并行流。

class WordCounterSpliterator implements Spliterator<Character> {

????private final String string;

????private int currentChar = 0;

????public WordCounterSpliterator(String string) {this.string = string;}

????//把String中當(dāng)前位置的Character傳給了Consumer帽借,并讓位置加一,如果新的指針位置小于String的總長(zhǎng)珠增,且還有要遍歷的Character,則 tryAdvance返回true砍艾。

????@Override? ?

????public boolean tryAdvance(Consumer<? super Character> action) {

????????action.accept(string.charAt(currentChar++));//處理當(dāng)前字符

????????return currentChar < string.length(); //如果還有字符要處理蒂教,則返回true

????}

????//定義拆分要遍歷的數(shù)據(jù)結(jié)構(gòu)的邏輯

????@Override

????public Spliterator<Character> trySplit() {

????????int currentSize = string.length() - currentChar;

????????if (currentSize < 10) {return null;}

????????for (int splitPos = currentSize / 2 + currentChar;splitPos < string.length(); splitPos++) { //將試探拆分位置設(shè)定為要解析的String的中間

????????????if (Character.isWhitespace(string.charAt(splitPos))) { //讓拆分位置前進(jìn)直到下 一個(gè)空格

????????????????Spliterator<Character> spliterator =

????????????????????new WordCounterSpliterator(string.substring(currentChar, splitPos));//創(chuàng)建一個(gè)新WordCounterSpliterator來(lái)解析String從開始到拆分位置的部分

????????????????currentChar = splitPos;//將這個(gè)WordCounterSpliterator 的起始位置設(shè)為拆分位置

????????????????return spliterator;

????????????}

????????}

????????return null;

????}

????//Spliterator解析的String的總長(zhǎng)度和當(dāng)前遍歷的位置的差

????@Override

????public long estimateSize() {return string.length() - currentChar;}

????@Override

????public int characteristics() {

????????return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;

????}

}

tryAdvance方法把String中當(dāng)前位置的Character傳給了Consumer,并讓位置加一脆荷。作為參數(shù)傳遞的Consumer是一個(gè)Java內(nèi)部類凝垛,在遍歷流時(shí)將要處理的Character傳給了一系列要對(duì)其執(zhí)行的函數(shù)懊悯。

trySplit方法是Spliterator中最重要的一個(gè)方法,它定義了拆分要遍歷的數(shù)據(jù)結(jié)構(gòu)的邏輯梦皮。就像分支/合并的例子那樣炭分,你肯定要用更高的下限來(lái)避免生成太多的任務(wù)。如果剩余的Character數(shù)量低于下限剑肯,你就返回null表示無(wú)需進(jìn)一步拆分捧毛。相 反,如果你需要執(zhí)行拆分让网,就把試探的拆分位置設(shè)在要解析的String塊的中間呀忧。一旦找到了適當(dāng)?shù)牟鸱治恢茫涂梢詣?chuàng)建一個(gè)新的Spliterator來(lái)遍歷從當(dāng)前位置到拆分位置的子串溃睹;把當(dāng)前位置this設(shè)為拆分位置而账,因?yàn)橹暗牟糠謱⒂尚?Spliterator來(lái)處理,最后返回丸凭。

還需要遍歷的元素的estimatedSize就是這個(gè)Spliterator解析的String的總長(zhǎng)度和當(dāng)前遍歷的位置的差福扬。

最后,characteristic方法告訴框架這個(gè)Spliterator是ORDERED(順序就是String中各個(gè)Character的次序)惜犀、SIZED(estimatedSize方法的返回值是精確的) 、 SUBSIZED(trySplit方法創(chuàng)建的其他Spliterator也有確切大泻莨)虽界、NONNULL(String中不能有為null的Character)和IMMUTABLE的(在解析String時(shí)不能再添加Character,因?yàn)镾tring本身是一個(gè)不可變類)涛菠。

運(yùn)用WordCounterSpliterator:

Spliterator<Character> spliterator = new WordCounterSpliterator(SENTENCE); Stream<Character> stream = StreamSupport.stream(spliterator, true); System.out.println("Found " + countWords(stream) + " words");

????--以上示例摘自《Java8實(shí)戰(zhàn)》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莉御,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俗冻,更是在濱河造成了極大的恐慌礁叔,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迄薄,死亡現(xiàn)場(chǎng)離奇詭異琅关,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)讥蔽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門涣易,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人冶伞,你說(shuō)我怎么就攤上這事新症。” “怎么了响禽?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵徒爹,是天一觀的道長(zhǎng)荚醒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)隆嗅,這世上最難降的妖魔是什么腌且? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮榛瓮,結(jié)果婚禮上铺董,老公的妹妹穿的比我還像新娘。我一直安慰自己禀晓,他們只是感情好精续,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粹懒,像睡著了一般重付。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凫乖,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天确垫,我揣著相機(jī)與錄音,去河邊找鬼帽芽。 笑死删掀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的导街。 我是一名探鬼主播披泪,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搬瑰!你這毒婦竟也來(lái)了款票?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泽论,失蹤者是張志新(化名)和其女友劉穎艾少,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翼悴,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缚够,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抄瓦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潮瓶。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖钙姊,靈堂內(nèi)的尸體忽然破棺而出毯辅,到底是詐尸還是另有隱情,我是刑警寧澤煞额,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布思恐,位于F島的核電站沾谜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胀莹。R本人自食惡果不足惜基跑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望描焰。 院中可真熱鬧媳否,春花似錦、人聲如沸荆秦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)步绸。三九已至掺逼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓤介,已是汗流浹背吕喘。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刑桑,地道東北人氯质。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像漾月,于是被迫代替她去往敵國(guó)和親病梢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容