Java8中的流操作-基本使用性能测试
一、流(Stream)簡介
流是 Java8 中 API 的新成員,它允許你以聲明式的方式處理數(shù)據(jù)集合(通過查詢語句來表達(dá),而不是臨時(shí)編寫一個(gè)實(shí)現(xiàn))。這有點(diǎn)兒像是我們操作數(shù)據(jù)庫一樣,例如我想要查詢出熱量較低的菜品名字我就可以像下面這樣:
SELECT name FROM dishes WHERE calorie < 400;您看,我們并沒有對菜品的什么屬性進(jìn)行篩選(比如像之前使用迭代器一樣每個(gè)做判斷),我們只是表達(dá)了我們想要什么。那么為什么到了 Java 的集合中,這樣做就不行了呢?
另外一點(diǎn),如果我們想要處理大量的數(shù)據(jù)又該怎么辦?是否是考慮使用多線程進(jìn)行并發(fā)處理呢?如果是,那么可能編寫的關(guān)于并發(fā)的代碼比使用迭代器本身更加的復(fù)雜,而且調(diào)試起來也會(huì)變得麻煩。
基于以上的幾點(diǎn)考慮,Java 設(shè)計(jì)者在 Java 8 版本中,引入了流的概念,來幫助您節(jié)約時(shí)間!并且有了 lambda 的參與,流操作的使用將更加順暢!
特點(diǎn)一:內(nèi)部迭代
就現(xiàn)在來說,您可以把它簡單的當(dāng)成一種高級(jí)的迭代器(Iterator),或者是高級(jí)的 for 循環(huán),區(qū)別在于,前面兩者都是屬于外部迭代,而流采用內(nèi)部迭代。
上圖簡要說明了內(nèi)部迭代與外部迭代的差異,我們再舉一個(gè)生活中實(shí)際的例子(引自《Java 8 實(shí)戰(zhàn)》),比如您想讓您兩歲的孩子索菲亞把她的玩具都收到盒子里面去,你們之間可能會(huì)產(chǎn)生如下的對話:
- 你:“索菲亞,我們把玩具收起來吧,地上還有玩具嗎?”
- 索菲亞:“有,球。”
- 你:“好,把球放進(jìn)盒子里面吧,還有嗎?”
- 索菲亞:“有,那是我的娃娃。”
- 你:“好,把娃娃也放進(jìn)去吧,還有嗎?”
- 索菲亞:“有,有我的書。”
- 你:“好,把書也放進(jìn)去,還有嗎?”
- 索菲亞:“沒有了。”
- 你:“好,我們收好啦。”
這正是你每天都要對 Java 集合做的事情。你外部迭代了一個(gè)集合,顯式地取出每個(gè)項(xiàng)目再加以處理,但是如果你只是跟索菲亞說:“把地上所有玩具都放進(jìn)盒子里”,那么索菲亞就可以選擇一手拿娃娃一手拿球,或是選擇先拿離盒子最近的那個(gè)東西,再拿其他的東西。
采用內(nèi)部迭代,項(xiàng)目可以透明地并行處理,或者用優(yōu)化的順序進(jìn)行處理,要是使用 Java 過去的外部迭代方法,這些優(yōu)化都是很困難的。
這或許有點(diǎn)雞蛋里挑骨頭,但這差不多就是 Java 8 引入流的原因了——Streams 庫的內(nèi)部迭代可以自動(dòng)選擇一種是和你硬件的數(shù)據(jù)表示和并行實(shí)現(xiàn)。
特點(diǎn)二:只能遍歷一次
請注意,和迭代器一樣,流只能遍歷一次。當(dāng)流遍歷完之后,我們就說這個(gè)流已經(jīng)被消費(fèi)掉了,你可以從原始數(shù)據(jù)那里重新獲得一條新的流,但是卻不允許消費(fèi)已消費(fèi)掉的流。例如下面代碼就會(huì)拋出一個(gè)異常,說流已被消費(fèi)掉了:
List<String> title = Arrays.asList("Wmyskxz", "Is", "Learning", "Java8", "In", "Action"); Stream<String> s = title.stream(); s.forEach(System.out::println); s.forEach(System.out::println); // 運(yùn)行上面程序會(huì)報(bào)以下錯(cuò)誤 /* Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closedat java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)at Test1.main(Tester.java:17) */特點(diǎn)三:方便的并行處理
Java 8 中不僅提供了方便的一些流操作(比如過濾、排序之類的),更重要的是對于并行處理有很好的支持,只需要加上?.parallel()?就行了!例如我們使用下面程序來說明一下多線程流操作的方便和快捷,并且與單線程做了一下對比:
public class StreamParallelDemo {/** 總數(shù) */private static int total = 100_000_000;public static void main(String[] args) {System.out.println(String.format("本計(jì)算機(jī)的核數(shù):%d", Runtime.getRuntime().availableProcessors()));// 產(chǎn)生1000w個(gè)隨機(jī)數(shù)(1 ~ 100),組成列表Random random = new Random();List<Integer> list = new ArrayList<>(total);for (int i = 0; i < total; i++) {list.add(random.nextInt(100));}long prevTime = getCurrentTime();list.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);System.out.println(String.format("單線程計(jì)算耗時(shí):%d", getCurrentTime() - prevTime));prevTime = getCurrentTime();// 只需要加上 .parallel() 就行了list.stream().parallel().reduce((a, b) -> a + b).ifPresent(System.out::println);System.out.println(String.format("多線程計(jì)算耗時(shí):%d", getCurrentTime() - prevTime));}private static long getCurrentTime() {return System.currentTimeMillis();} }以上程序分別使用了單線程流和多線程流計(jì)算了一千萬個(gè)隨機(jī)數(shù)的和,輸出如下:
本計(jì)算機(jī)的核數(shù):8
655028378
單線程計(jì)算耗時(shí):4159
655028378
多線程計(jì)算耗時(shí):540
并行流的內(nèi)部使用了默認(rèn)的 ForkJoinPool 分支/合并框架,它的默認(rèn)線程數(shù)量就是你的處理器數(shù)量,這個(gè)值是由?Runtime.getRuntime().availableProcessors()?得到的(當(dāng)然我們也可以全局設(shè)置這個(gè)值)。我們也不再去過度的操心加鎖線程安全等一系列問題。
二、流基本操作
至少我們從上面了解到了,流操作似乎是一種很強(qiáng)大的工具,能夠幫助我們節(jié)約我們時(shí)間的同時(shí)讓我們程序可讀性更高,下面我們就具體的來了解一下 Java 8 帶來的新 API Stream,能給我們帶來哪些操作。
1、篩選和切片
filter
Stream 接口支持?filter?方法,該操作會(huì)接受一個(gè)返回 boolean 的函數(shù)作為參數(shù),并返回一個(gè)包含所有符合該條件的流。例如,你可以這樣選出所有以字母 w 開頭的單詞并打印:
List<String> words = Arrays.asList("wmyskxz", "say", "wow", "to", "everybody"); words.stream().filter(word -> word.startsWith("w")).forEach(System.out::println); // ==============輸出:=============== // wmyskxz // wow這個(gè)過程類似下圖:
當(dāng)然如果您不是想要輸出而是想要返回一個(gè)集合,那么可以使用?.collect(toList()),就像下面這樣:
List<String> words = Arrays.asList("wmyskxz", "say", "wow", "to", "everybody"); List<String> filteredWords = words.stream().filter(word -> word.startsWith("w")).collect(Collectors.toList());distinct
流還支持一個(gè)叫做?distinct?的方法,它會(huì)返回一個(gè)元素各異(根據(jù)流所生成的元素的 hashCode 和 equals 方法實(shí)現(xiàn))的流。例如,以下代碼會(huì)篩選出列表中所有的偶數(shù),并確保沒有重復(fù):
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 2, 1, 3, 4); numbers.stream().filter(integer -> integer % 2 == 0).distinct().forEach(System.out::println); // ==============輸出:=============== // 2 // 4limit
流支持?limit(n)?方法,該方法會(huì)返回一個(gè)不超過給定長度的流,所需長度需要作為參數(shù)傳遞給 limit。如果流是有序的,則最多會(huì)返回前 n 個(gè)元素。比如,你可以建立一個(gè) List,選出前 3 個(gè)元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); numbers.stream().filter(integer -> integer % 2 == 0).limit(3).forEach(System.out::println); // ==============輸出:=============== // 2 // 4 // 6請注意雖然上述的集合是有序的,但 limit 本身并不會(huì)做任何排序的操作。
skip
流還支持?skip(n)?方法,返回一個(gè)扔掉了前 n 個(gè)元素的流。如果流中元素不足 n 個(gè),則返回一個(gè)空流。請注意 litmit 和 skip 是互補(bǔ)的!例如,下面這段程序,選出了所有的偶數(shù)并跳過了前兩個(gè)輸出:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); numbers.stream().filter(integer -> integer % 2 == 0).skip(2).forEach(System.out::println); // ==============輸出:=============== // 6 // 82、映射
一個(gè)非常常見的數(shù)據(jù)處理套路就是從某些對象中選擇信息。比如在 SQL 里,你可以從表中選擇一列,Stream API 也通過 map 和 flatMap 方法提供了類似的工具。
map
流支持 map 方法,他會(huì)接受一個(gè)函數(shù)作為參數(shù)。這個(gè)函數(shù)會(huì)被應(yīng)用到每個(gè)元素身上嗎,并將其映射成一個(gè)新的函數(shù)。例如,下面的代碼把方法引用?Words::getContent?傳給了 map 方法,來提取流中 Words 的具體內(nèi)容:
public static void main(String[] args) {List<Words> numbers = Arrays.asList(new Words("我沒有三顆心臟"),new Words("公眾號(hào)"), new Words("wmyskxz"));numbers.stream().map(Words::getContent).forEach(System.out::println); }@Data @AllArgsConstructor private static class Words {private String content; } // ==============輸出:=============== // 我沒有三顆心臟 // 公眾號(hào) // wmyskxz但是如果你現(xiàn)在只想要找出每個(gè) Words 具體內(nèi)容的長度又該怎么辦呢?我們可以再進(jìn)行一次映射:
public static void main(String[] args) {List<Words> numbers = Arrays.asList(new Words("我沒有三顆心臟"),new Words("公眾號(hào)"), new Words("wmyskxz"));numbers.stream().map(Words::getWords).map(String::length).forEach(System.out::println); }@Data @AllArgsConstructor private static class Words {private String words; } // ==============輸出:=============== // 7 // 3 // 7flatMap:流的扁平化
你已經(jīng)看到我們是如何使用 map 方法來返回每個(gè) Words 的具體長度了,現(xiàn)在讓我們來擴(kuò)展一下:對于一個(gè) Words 集合,我需要知道這個(gè)集合里一共有多少個(gè)不相同的字符呢?例如,給定單詞列表為:["Hello", "World"],則需要返回的列表是:["H", "e", "l", "o", "W", "r", "d"]。
您可能會(huì)覺得簡單,而后寫下下列錯(cuò)誤的第一版本:
List<String> words = Arrays.asList("Hello", "World"); words.stream().map(s -> s.split("")).distinct().collect(Collectors.toList()).forEach(System.out::println); // ==============輸出:=============== // [Ljava.lang.String;@238e0d81 // [Ljava.lang.String;@31221be2為什么會(huì)這樣呢?這個(gè)方法的問題自傲與,傳遞給 map 方法的 lambda 表達(dá)式為每個(gè)單詞返回了一個(gè)?String[],所以經(jīng)過 map 方法之后返回的流就不是我們預(yù)想的?Stream<String>,而是?Stream<String[]>,下圖就說明了這個(gè)問題:
幸好我們可以使用?flatMap?來解決這個(gè)問題:
List<String> words = Arrays.asList("Hello", "World"); words.stream().map(s -> s.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList()).forEach(System.out::println); // ==============輸出:=============== // H // e // l // o // W // r // d使用 flatMap 方法的效果是,各個(gè)數(shù)組并不是分別映射成一個(gè)流,而是映射成流的內(nèi)容。一言蔽之就是 flatMap 讓你一個(gè)流中的每個(gè)值都轉(zhuǎn)換成另一個(gè)六,然后把所有的流連接起來成為一個(gè)流,具體過程如下圖:
3、查找和匹配
另一個(gè)常見的數(shù)據(jù)處理套路是看看數(shù)據(jù)集中的某些元素是否匹配一個(gè)給定的屬性,Stream API 通過?allMatch、anyMatch、noneMatch、findFirst?和?findAny?方法提供了這樣的工具(其實(shí)到這里看名字就會(huì)大概能夠知道怎么使用了)。
我們簡單的舉兩個(gè)例子就好。
比如,你可以用它來看看集合里面是否有偶數(shù):
List<Integer> numbers = Arrays.asList(1, 2, 3); if (numbers.stream().anyMatch(i -> i % 2 == 0)) {System.out.println("集合里有偶數(shù)!"); }再比如,你可以用來它來檢驗(yàn)是否集合里都為偶數(shù):
List<Integer> numbers = Arrays.asList(2, 2, 4); if (numbers.stream().allMatch(i -> i % 2 == 0)) {System.out.println("集合里全是偶數(shù)!"); }再或者,給定一個(gè)數(shù)字列表,找出第一個(gè)平方能被 3 整除的數(shù):
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7); Optional<Integer> firstSquareDivisibledByThree =numbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst(); System.out.println(firstSquareDivisibledByThree.get()); // ==============輸出:=============== // 9Optional 簡介:
Optional<T>?類是?java.util.Optional?包里的一個(gè)容器類,代表一個(gè)值存在或者不存在。在上面的代碼中,findFirst()?可能什么元素都找不到,Java 8 的設(shè)計(jì)人員引入了?Optional<T>,這樣就不用返回眾所周知容易出問題的 null 了。我們在這里不對?Optional?做細(xì)致的討論。
4、歸約:reduce
到目前為止,你見到過的終端操作(下面我們會(huì)說到這些操作其實(shí)分為中間操作和終端操作)都是返回一個(gè) boolean(allMatch?之類的)、void(forEach)或 Optional 對象(findFirst?等)。你也見到過了使用?collect?來將流中的所有元素合并成一個(gè) List。
接下來我們來接觸更加復(fù)雜的一些操作,比如 “挑出單詞中長度最長的的單詞” 或是 “計(jì)算所有單詞的總長度”。此類查詢需要將流中的元素反復(fù)結(jié)合起來,得到一個(gè)值。這樣的查詢可以被歸類為歸約操作(將流歸約成一個(gè)值)。
數(shù)組求和
在研究 reduce 之前,我們先來回顧一下我們在之前是如何對一個(gè)數(shù)字?jǐn)?shù)組進(jìn)行求和的:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = 0; for (int x : numbers) {sum += x; } System.out.println(sum); // ==============輸出:=============== // 15numbers 中的每個(gè)元素都用加法運(yùn)算符反復(fù)迭代來得到結(jié)果。通過反復(fù)使用加法,我們最終把一個(gè)數(shù)字列表歸約成了一個(gè)數(shù)字。在這段代碼中,我們一共使用了兩個(gè)參數(shù):
- sum:總和變量的初始值,在這里是 0;
- x:用于接受 numbers 中的每一個(gè)元素,并與 sum 做加法操作不斷迭代;
要是還能把所有的數(shù)字相乘,而不用復(fù)制粘貼這段代碼,豈不是很好?這正是 reduce 操作的用武之地,它對這種重復(fù)應(yīng)用的模式做了抽象。你可以像下面這樣對流中所有的元素求和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream().reduce(0, (a, b) -> a + b); System.out.println(sum); // ==============輸出:=============== // 15其中 reduce 接受了兩個(gè)參數(shù):
- 一個(gè)初始值,這里是 0;
- 一個(gè)是?BinaryOperator<T>?來將兩個(gè)元素結(jié)合起來產(chǎn)生一個(gè)新值,這里我們用的是?lambda (a, b) -> a + b;
你也可以很容易改造成所有元素相乘的形式,只需要將另一個(gè) Lambda:(a, b) -> a * b?傳遞給 reduce 就可以了:
int product = numbers.stream().reduce(0, (a, b) -> a * b);我們先來深入研究一下 reduce 是如何對一個(gè)數(shù)字流進(jìn)行求和的:
如上圖所示一樣的,reduce 每一次都把結(jié)果返回并與下一次的元素進(jìn)行操作,比如第一次當(dāng)遍歷到元素 1 時(shí),此時(shí)返回初始值 0 + 1 = 1,然后再用此時(shí)的返回值 1 與第二個(gè)元素進(jìn)行疊加操作,如此往復(fù),便完成了對數(shù)字列表的求和運(yùn)算。
當(dāng)然你也可以使用方法引用讓這段代碼更加簡潔:
int sum = numbers.stream().reduce(0, Integer::sum);無初始值
reduce 還有一個(gè)重載的變體,它不接受初始值,但是會(huì)返回一個(gè) Optional 對象(考慮到流中沒有任何元素的情況):
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);最大值和最小值
有點(diǎn)類似于上面的操作,我們可以使用下面這樣的 reduce 來計(jì)算流中的最大值or最小值:
// 最大值 Optional<Integer> max = numbers.stream().reduce(Integer::max); // 最小值 Optional<Integer> max = numbers.stream().reduce(Integer::min);5、中間操作和結(jié)束操作(終端操作)
Stream API 上的所有操作分為兩類:中間操作和結(jié)束操作。中間操作只是一種標(biāo)記,只有結(jié)束操作才會(huì)觸發(fā)實(shí)際計(jì)算。
中間操作又可以分為無狀態(tài)的(Stateless)和有狀態(tài)的(Stateful),無狀態(tài)中間操作是指元素的處理不受前面元素的影響,而有狀態(tài)的中間操作必須等到所有元素處理之后才知道最終結(jié)果,比如排序是有狀態(tài)操作,在讀取所有元素之前并不能確定排序結(jié)果;
結(jié)束操作又可以分為短路操作和非短路操作,短路操作是指不用處理全部元素就可以返回結(jié)果,比如找到第一個(gè)滿足條件的元素。之所以要進(jìn)行如此精細(xì)的劃分,是因?yàn)榈讓訉γ恳环N情況的處理方式不同。 為了更好的理解流的中間操作和終端操作,可以通過下面的兩段代碼來看他們的執(zhí)行過程:
IntStream.range(1, 10).peek(x -> System.out.print("\nA" + x)).limit(3).peek(x -> System.out.print("B" + x)).forEach(x -> System.out.print("C" + x)); // ==============輸出:=============== // A1B1C1 // A2B2C2 // A3B3C3中間操作是懶惰的,也就是不會(huì)對數(shù)據(jù)做任何操作,直到遇到了結(jié)束操作。而結(jié)束操作都是比較熱情的,他們會(huì)回溯之前所有的中間操作。
拿上面的例子來說,當(dāng)執(zhí)行到?forEach()?的時(shí)候,它會(huì)回溯到上一步中間操作,再到上一步中間操作,再上一步..直到第一步,也就是這里的?.peek(x -> System.out.println("\nA" + x),然后開始自上而下的依次執(zhí)行,輸出第一行的?A1B1C1,然而第二次執(zhí)行?forEach()?操作的時(shí)候等同,以此類推..
我們再來看第二段代碼:
IntStream.range(1, 10).peek(x -> System.out.print("\nA" + x)).skip(6).peek(x -> System.out.print("B" + x)).forEach(x -> System.out.print("C" + x)); // ==============輸出:=============== // A1 // A2 // A3 // A4 // A5 // A6 // A7B7C7 // A8B8C8 // A9B9C9根據(jù)上面介紹的規(guī)則,同樣的當(dāng)?shù)谝淮螆?zhí)行?.forEach()?的時(shí)候,會(huì)回溯到第一個(gè)?peek?操作,打印出?A1,然后執(zhí)行?skip,這個(gè)操作的意思就是跳過,也就是相當(dāng)于 for 循環(huán)里面的 continue,所以前六次的?forEach()?操作都只會(huì)打印 A。
而第七次開始,skip?失效之后,就會(huì)開始分別執(zhí)行?.peek()?和?forEach()?里面的打印語句了,就會(huì)看到輸出的是:A7B7C7。
OK,到這里也算是對 Stream API 有了一定的認(rèn)識(shí),下面我們對中間操作和結(jié)束操作做一個(gè)總結(jié):
- 圖片截自:https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/6-Stream%20Pipelines.md
三、Stream 性能測試
引用自:下方參考文檔第 4 條。
已經(jīng)對 Stream API 的用法鼓吹夠多了,用起簡潔直觀,但性能到底怎么樣呢?會(huì)不會(huì)有很高的性能損失?本節(jié)我們對 Stream API 的性能一探究竟。
為保證測試結(jié)果真實(shí)可信,我們將 JVM 運(yùn)行在?-server?模式下,測試數(shù)據(jù)在 GB 量級(jí),測試機(jī)器采用常見的商用服務(wù)器,配置如下:
| OS | CentOS 6.7 x86_64 |
| CPU | Intel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads |
| 內(nèi)存 | 96GB |
| JDK | java version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM |
測試所用代碼在這里,測試結(jié)果匯總.
測試方法和測試數(shù)據(jù)
性能測試并不是容易的事,Java性能測試更費(fèi)勁,因?yàn)樘摂M機(jī)對性能的影響很大,JVM對性能的影響有兩方面:
Stream并行執(zhí)行時(shí)用到ForkJoinPool.commonPool()得到的線程池,為控制并行度我們使用Linux的taskset命令指定JVM可用的核數(shù)。
測試數(shù)據(jù)由程序隨機(jī)生成。為防止一次測試帶來的抖動(dòng),測試4次求出平均時(shí)間作為運(yùn)行時(shí)間。
實(shí)驗(yàn)一 基本類型迭代
測試內(nèi)容:找出整型數(shù)組中的最小值。對比for循環(huán)外部迭代和Stream API內(nèi)部迭代性能。
測試程序IntTest,測試結(jié)果如下圖:
圖中展示的是for循環(huán)外部迭代耗時(shí)為基準(zhǔn)的時(shí)間比值。分析如下:
對于基本類型Stream串行迭代的性能開銷明顯高于外部迭代開銷(兩倍);
Stream并行迭代的性能比串行迭代和外部迭代都好。
并行迭代性能跟可利用的核數(shù)有關(guān),上圖中的并行迭代使用了全部 12 個(gè)核,為考察使用核數(shù)對性能的影響,我們專門測試了不同核數(shù)下的Stream并行迭代效果:
分析,對于基本類型:
以上兩個(gè)測試說明,對于基本類型的簡單迭代,Stream串行迭代性能更差,但多核情況下Stream迭代時(shí)性能較好。
實(shí)驗(yàn)二 對象迭代
再來看對象的迭代效果。
測試內(nèi)容:找出字符串列表中最小的元素(自然順序),對比for循環(huán)外部迭代和Stream API內(nèi)部迭代性能。
測試程序StringTest,測試結(jié)果如下圖:
結(jié)果分析如下:
再來單獨(dú)考察Stream并行迭代效果:
分析,對于對象類型:
以上兩個(gè)測試說明,對于對象類型的簡單迭代,Stream串行迭代性能更差,但多核情況下Stream迭代時(shí)性能較好。
實(shí)驗(yàn)三 復(fù)雜對象歸約
從實(shí)驗(yàn)一、二的結(jié)果來看,Stream串行執(zhí)行的效果都比外部迭代差(很多),是不是說明Stream真的不行了?先別下結(jié)論,我們再來考察一下更復(fù)雜的操作。
測試內(nèi)容:給定訂單列表,統(tǒng)計(jì)每個(gè)用戶的總交易額。對比使用外部迭代手動(dòng)實(shí)現(xiàn)和Stream API之間的性能。
我們將訂單簡化為<userName, price, timeStamp>構(gòu)成的元組,并用Order對象來表示。測試程序ReductionTest,測試結(jié)果如下圖:
分析,對于復(fù)雜的歸約操作:
再來考察并行度對并行效果的影響,測試結(jié)果如下:
分析,對于復(fù)雜的歸約操作:
以上兩個(gè)實(shí)驗(yàn)說明,對于復(fù)雜的歸約操作,Stream串行歸約效果好于手動(dòng)歸約,在多核情況下,并行歸約效果更佳。我們有理由相信,對于其他復(fù)雜的操作,Stream API也能表現(xiàn)出相似的性能表現(xiàn)。
結(jié)論
上述三個(gè)實(shí)驗(yàn)的結(jié)果可以總結(jié)如下:
所以,如果出于性能考慮,1. 對于簡單操作推薦使用外部迭代手動(dòng)實(shí)現(xiàn),2. 對于復(fù)雜操作,推薦使用Stream API, 3. 在多核情況下,推薦使用并行Stream API來發(fā)揮多核優(yōu)勢,4.單核情況下不建議使用并行Stream API。
如果出于代碼簡潔性考慮,使用Stream API能夠?qū)懗龈痰拇a。即使是從性能方面說,盡可能的使用Stream API也另外一個(gè)優(yōu)勢,那就是只要Java Stream類庫做了升級(jí)優(yōu)化,代碼不用做任何修改就能享受到升級(jí)帶來的好處。
參考文檔
按照慣例黏一個(gè)尾巴:
歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處!
獨(dú)立域名博客:wmyskxz.com
簡書ID:@我沒有三顆心臟
github:wmyskxz
總結(jié)
以上是生活随笔為你收集整理的Java8中的流操作-基本使用性能测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 面试知识点解析(七)——Web
- 下一篇: Kafka【入门】就这一篇!