1. 程式人生 > 其它 >lambda表示式知識點補充

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),生成最後的執行結果,整體執行流程大致如下:

下面是一段示例程式碼,在程式碼中我們先初始化了一個長度為10000List,然後分別通過普通的streamparallelStream分別遍歷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倍,這資料就足以說明parallelStreamstream效能要好,當然具體的資料還是取決於你的電腦效能。

但是需要注意的是,由於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分組,然後生成一個Mapmapkey是我們分組的欄位,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,然後先通過groupingByTypeId分組,最終結果如下:

可以看到,最後生成的map就是以TypeIdkey進行分組的。

下面的程式碼是多欄位分組的寫法:

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表示式,那它只會讓你受益無窮。好了,今天內容就到這裡吧,大家晚安!