lambda表示式知識點補充
前言
截止到昨天,我們已經分享完了spring cloud
的核心模組的相關知識點,下一步的想法是繼續深挖spring
相關的知識點,或者開始分享dubbo
的知識點,或者分享Netty
的相關知識點。
單從目前我對以上知識的掌握來看,分享前兩種知識點的可能性比較大,但是後一種我覺得還有很有難度的,因為我對netty
的積累還不夠,所以暫時可能不會分享。目前我比較傾向於dubbo
相關知識點的分享,主要有兩個原因,一個是對於dubbo
我的知識盲區點比較多,系統地學習一下dubbo
剛好可以查漏補缺;另一個就是最近一直在分享spring
相關知識點,有點上頭,所以暫時換個頻道。
好了,關於未來幾天的內容分享計劃,我們暫時就先說這麼多,下面插播一期lambda
lambda表示式
關於lambda
表示式,我們之前也有分享過幾期內容,但由於這一塊的知識比較零散,而且內容比較多,很難一次分享完,所以今天我們在原來分享的基礎上,再來做一些補充,下面我們直接開始吧。
parallelStream
可能很多小夥伴只知道stream
,並不知道parallelStream
,其實,我以前也只知道steam
,後來有一次和一個同事討論一個技術問題,他推薦我使用parallelStream
。當然,當時只管著用,就沒有仔細研究過,知道最近遇到了一個問題,我才真正去查了一些資料,瞭解了parallelStream
的相關知識點。
parallelStream
中文含義併發流、平行流,和stream
fork/join
機制,即把一個大任務拆分(fork
)成多個子任務,子任務之間還會繼續拆分,然後以多執行緒的方式去執行,最後執行結束後,將子任務結果進行整合(join
),生成最後的執行結果,整體執行流程大致如下:
下面是一段示例程式碼,在程式碼中我們先初始化了一個長度為10000
的List
,然後分別通過普通的stream
和 parallelStream
分別遍歷integers
,然後把其中的值轉換成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); }
執行結果如下:
根據本次示例執行結果,我們可以發現parallelStream
比普通的stream
快了32
倍,這資料就足以說明parallelStream
比stream
效能要好,當然具體的資料還是取決於你的電腦效能。
但是需要注意的是,由於parallelStream
是多個子任務同時執行的,所以它本身是執行緒不安全的,而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("原始資料:" + integers.size());
integers.stream().forEach(i -> {
// integers.parallelStream().forEach(i -> {
index.incrementAndGet();
integersList.add(i);
});
System.out.println("處理完成後:integerList.size:" + integersList.size());
}
同樣的程式碼,如果用stream()
,返回結果就是正常的:
但如果用parallelStream
,這結果就有點離譜了,資料一半都被弄丟了:
如果parallelStream
內部如果多加一行列印,結果會稍微好一點,但是也是有問題的:
導致執行緒不安全問題的原因是因為我們parallelStream
內部用到了integersList
這個共享變數,如果你的parallelStream
內部沒有寫相關的操作,那應該是不存線上程安全問題的,總之,慎用parallelStream
。
另外還有一點需要注意,那就是parallelStream
無法確保執行結果的有序性,雖然在本次執行結果中,它和stream
的執行結果是一致的,但是在某些情況下,它的結果是無序的,如果你對順序有要求,那可能需要你自己重新排序。
groupBy
groupBy
也算是一個比較常用的lambda
表示式了,我們經常用它來對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
分組,最終結果如下:
可以看到,最後生成的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);
執行結果如下:
根據執行結果可以看出,我們的map
已經按ProjectNum.Sort
的形式為我們分組了,我覺得這種方式最常用,特別是在複雜業務中,資料關係比較複雜的話,用這種方式可以很方便地構建出我們需要的資料格式。
總結
lambda
表示式用起來確實很方便,也確實很爽,使用它不僅可以讓我們的程式碼更簡潔,而且還提升我們系統性能,當然,它也有一些弊端,比如不便於問題排查(解決方式也很簡單,增加一些必要的日誌),但是我覺得只要你用好了lambda
表示式,那它只會讓你受益無窮。好了,今天內容就到這裡吧,大家晚安!