lambda表達式知識點補充

前言
截止到昨天,我們已經(jīng)分享完了spring cloud的核心模塊的相關(guān)知識點,下一步的想法是繼續(xù)深挖spring相關(guān)的知識點,或者開始分享dubbo的知識點,或者分享Netty的相關(guān)知識點。
單從目前我對以上知識的掌握來看,分享前兩種知識點的可能性比較大,但是后一種我覺得還有很有難度的,因為我對netty的積累還不夠,所以暫時可能不會分享。目前我比較傾向于dubbo相關(guān)知識點的分享,主要有兩個原因,一個是對于dubbo我的知識盲區(qū)點比較多,系統(tǒng)地學(xué)習一下dubbo剛好可以查漏補缺;另一個就是最近一直在分享spring相關(guān)知識點,有點上頭,所以暫時換個頻道。
好了,關(guān)于未來幾天的內(nèi)容分享計劃,我們暫時就先說這么多,下面插播一期lambda相關(guān)的知識點。
lambda表達式
關(guān)于lambda表達式,我們之前也有分享過幾期內(nèi)容,但由于這一塊的知識比較零散,而且內(nèi)容比較多,很難一次分享完,所以今天我們在原來分享的基礎(chǔ)上,再來做一些補充,下面我們直接開始吧。
parallelStream
可能很多小伙伴只知道stream,并不知道parallelStream,其實,我以前也只知道steam,后來有一次和一個同事討論一個技術(shù)問題,他推薦我使用parallelStream。當然,當時只管著用,就沒有仔細研究過,知道最近遇到了一個問題,我才真正去查了一些資料,了解了parallelStream的相關(guān)知識點。
parallelStream中文含義并發(fā)流、平行流,和stream相比,它效率更高,內(nèi)部采用了 fork/join機制,即把一個大任務(wù)拆分(fork)成多個子任務(wù),子任務(wù)之間還會繼續(xù)拆分,然后以多線程的方式去運行,最后運行結(jié)束后,將子任務(wù)結(jié)果進行整合(join),生成最后的運行結(jié)果,整體運行流程大致如下:

下面是一段示例代碼,在代碼中我們先初始化了一個長度為10000的List,然后分別通過普通的stream和 parallelStream分別遍歷integers,然后把其中的值轉(zhuǎn)換成string類型,放入一個新的集合中,這里用了map(String::valueOf),最后分別打印他們的運行時間,比較他們的性能
private static void parallelStreamTest2() {
List<Integer> integers = Lists.newArrayList();
Random random = new Random(100);
for (int i = 0; i < 10000; i++) {
integers.add(random.nextInt(100));
}
long start = System.currentTimeMillis();
List<String> collect1 = integers.parallelStream().map(String::valueOf).collect(Collectors.toList());
System.out.println("collect1 用時:" + (System.currentTimeMillis() - start));
long start2 = System.currentTimeMillis();
List<String> collect2 = integers.stream().map(String::valueOf).collect(Collectors.toList());
System.out.println("collect2 用時:" + (System.currentTimeMillis() - start2));
System.out.println("collect1: " + collect1);
System.out.println("collect2: " + collect2);
}
運行結(jié)果如下:

根據(jù)本次示例運行結(jié)果,我們可以發(fā)現(xiàn)parallelStream比普通的stream快了32倍,這數(shù)據(jù)就足以說明parallelStream比stream性能要好,當然具體的數(shù)據(jù)還是取決于你的電腦性能。
但是需要注意的是,由于parallelStream是多個子任務(wù)同時運行的,所以它本身是線程不安全的,而stream是串行運行所以線程是安全的。下面我們通過一段代碼來說明parallelStream的線程安全問題:
private static void parallelStreamTest() {
List<Integer> integers = Lists.newArrayList();
Random random = new Random(100);
for (int i = 0; i < 10000; i++) {
integers.add(random.nextInt(100));
}
AtomicInteger index = new AtomicInteger(0);
List<Integer> integersList = Lists.newArrayList();
System.out.println("原始數(shù)據(jù):" + integers.size());
integers.stream().forEach(i -> {
// integers.parallelStream().forEach(i -> {
index.incrementAndGet();
integersList.add(i);
});
System.out.println("處理完成后:integerList.size:" + integersList.size());
}
同樣的代碼,如果用stream(),返回結(jié)果就是正常的:

但如果用parallelStream,這結(jié)果就有點離譜了,數(shù)據(jù)一半都被弄丟了:

如果parallelStream內(nèi)部如果多加一行打印,結(jié)果會稍微好一點,但是也是有問題的:

導(dǎo)致線程不安全問題的原因是因為我們parallelStream內(nèi)部用到了integersList這個共享變量,如果你的parallelStream內(nèi)部沒有寫相關(guān)的操作,那應(yīng)該是不存在線程安全問題的,總之,慎用parallelStream。
另外還有一點需要注意,那就是parallelStream無法確保運行結(jié)果的有序性,雖然在本次運行結(jié)果中,它和stream的運行結(jié)果是一致的,但是在某些情況下,它的結(jié)果是無序的,如果你對順序有要求,那可能需要你自己重新排序。
groupBy
groupBy也算是一個比較常用的lambda表達式了,我們經(jīng)常用它來對List分組,然后生成一個Map,map的key是我們分組的字段,value就是我們分組后的List,下面我們先通過一個實例簡單演示下:
List<TestVo> testVoList = Lists.newArrayList();
testVoList.add(new TestVo(1L, 1, 111L, 10L, "test1"));
testVoList.add(new TestVo(2L, 2, 111L, 20L, "test2"));
testVoList.add(new TestVo(3L, 3, 111L, 30L, "test3"));
testVoList.add(new TestVo(4L, 1, 112L, 10L, "test4"));
testVoList.add(new TestVo(5L, 2, 112L, 20L, "test5"));
testVoList.add(new TestVo(6L, 3, 112L, 30L, "test6"));
testVoList.add(new TestVo(7L, 1, 113L, 10L, "test7"));
testVoList.add(new TestVo(8L, 2, 113L, 20L, "test8"));
testVoList.add(new TestVo(9L, 3, 113L, 30L, "test9"));
testVoList.add(new TestVo(10L, 4, 113L, 40L, "test10"));
System.out.println("初始化后:testVoList = " + testVoList);
Map<Long, List<TestVo>> typeIdTestVoMap = testVoList.stream().collect(Collectors.groupingBy(TestVo::getTypeId));
System.out.println("typeIdTestVoMap = " + typeIdTestVoMap);
public static class TestVo {
Long id;
Integer sort;
Long projectNum;
Long typeId;
String name;
}
在上面的代碼中,我們先初始化了一個testVoList,然后先通過groupingBy對TypeId分組,最終結(jié)果如下:

可以看到,最后生成的map就是以TypeId為key進行分組的。
下面的代碼是多字段分組的寫法:
Map<String, List<TestVo>> projectNUmSortMap = testVoList.stream().collect(Collectors.groupingBy(t -> String.format("%s.%s", t.getProjectNum(), t.getSort())));
System.out.println("projectNUmSortMap = " + projectNUmSortMap);
運行結(jié)果如下:

根據(jù)運行結(jié)果可以看出,我們的map已經(jīng)按ProjectNum.Sort的形式為我們分組了,我覺得這種方式最常用,特別是在復(fù)雜業(yè)務(wù)中,數(shù)據(jù)關(guān)系比較復(fù)雜的話,用這種方式可以很方便地構(gòu)建出我們需要的數(shù)據(jù)格式。
總結(jié)
lambda表達式用起來確實很方便,也確實很爽,使用它不僅可以讓我們的代碼更簡潔,而且還提升我們系統(tǒng)性能,當然,它也有一些弊端,比如不便于問題排查(解決方式也很簡單,增加一些必要的日志),但是我覺得只要你用好了lambda表達式,那它只會讓你受益無窮。好了,今天內(nèi)容就到這里吧,又是忙碌的一天

,大家晚安!
