天天看點

Java 8 流式處理提高程式響應

Java8背景介紹:

Java 8 在2014年3月釋出,現在公司内部Java相關的開發(包括伺服器端和安卓用戶端)所引用的JDK都是jdk1.8,但是幾乎項目中沒有使用Java 8的特性。

為什麼使用Java 8?

目前,不管是伺服器還是用戶端的CPU都是多核的,而在Java 8 之前的程式隻能使用一個核,除非利用多線程才會使用多個核心,而線程使用起來容易出現錯誤。

Java 8提供了一個新的API(稱為“流”,Stream),支援許多資料的并行操作。流式處理的特點,程式中可以從輸入流中一個一個讀取資料項,然後以同樣的方式将資料項寫入輸出流,一個程式的輸出流也可以作為另一個程式的輸入流。下面給出一個流式處理的流程圖:

Java 8 流式處理提高程式響應

上述流圖中,你可以把幾個基礎操作連結起來,來表達複雜的資料處理流水線(在filter後面接上sorted、map和collect操作,這些操作是流式自帶的方法),同時代碼保持清晰可讀。filter的結果被傳給sorted方法,再傳給map方法,最後傳給collect方法。即上述的代碼可寫為:

List<String> lowCar = menu.stream()
								.filter(d->d.getCalories()<400)
								.sorted(comparing(Dish::getCalories))
								.map(Dish::getName)
								.collect(toList());
           

由于流式處理的特點使得上一個環節處理完了部分資料,這部分已經處理好的資料可以馬上進入下一個環節處理,而不用等所有資料都處理完後再進入下一個環節。

除此之外,流式處理在共享資料不變的情況下,還允許開啟并行流模式,加快程式的運作速度。

什麼時候用Java 8 流式處理,什麼時候不要用?

舉個栗子:

對一個List<String> list中的元素進行判斷,如果字元串是數字則将字元串轉成數字加上1000,再轉為數字。

代碼如下:

public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch();
        List<String> list = new ArrayList<>();
        for(int i=0;i<1_000;i++){
            list.add("123");list.add("adf");list.add("h12");list.add("345345");list.add("12334");
        }
        //將list中字元串如果是數字則加上1000
        //傳統的寫法
        stopWatch.start();
        List<String> list2 = new ArrayList<>();
        for(String str:list){
            if(NumberUtils.isDigits(str)){
                int sum = NumberUtils.toInt(str) + 1000;
                list2.add(String.valueOf(sum));
            }else{
                list2.add(str);
            }
        }
        stopWatch.stop();
        System.out.println(stopWatch.taskCostTime("非java 8"));
        
        //java 8的寫法
        stopWatch.restart();
        List<String> list3 = list.stream().map(str->{
            if(NumberUtils.isDigits(str)){
                int sum = NumberUtils.toInt(str) + 1000;
                return String.valueOf(sum);
            }else{
                return str;
            }
        }).collect(Collectors.toList());
        stopWatch.stop();
        System.out.println(stopWatch.taskCostTime("java 8"));
}
           

結論分析:

一個統計的表格如下:

Java 8 流式處理提高程式響應

上述性能中橫坐标是測試的資料集個數,縱坐标是耗費的毫秒數。所有資料都是循環20次求平均值。

可以看到在資料集小于四百多萬時,Java 8 并行流性能稍微好于Java 8串行流,好于非Java 8,d當大于六百多萬時,反而仍是非Java 8 性能最好。

原因分析:

(1)由于上述字元串轉數字加上1000再轉回字元串,耗時極短(不到1ms)。

(2)且上述的處理環節隻有一個,相對于流式根本沒有什麼優勢。

(3)使用并行流反而效率更低,那是因為并行流的本質上是開啟多個線程,而線程的建立以及資料的合并的代價已經超過了(1)中資料處理的代價,是以在資料量一大,這個缺點被極具放大。

為了證明這個觀點,增加數字轉字元串加上1000花費的時間(Thread.sleep(2)),即該操作會慢2ms。

得到的統計資料表如下:

Java 8 流式處理提高程式響應

這裡非Java 8的性能和Java8串行的性能幾乎一緻,是以重合。

而Java 8 并行的性能已經遠遠好于其他兩種。

原因分析:為什麼Java8串行的性能與非java的性能一緻,因為,這裡的流的中間操作隻有一個,是以沒有利用流的特性(至少需要兩個),而Java 8的并行流,在處理第一個中間操作開啟多線程處理,是以能夠顯著提高性能,其處理耗時縮短10倍以上。

總結:

Java 8 引入的特性遠不止我們上面提到的流式處理,還包括行為參數化、lambda表達式,以及函數式程式設計等概念。

之是以重點強調流式處理,因為在流式進行中,流的來源可以是檔案或者函數生成流, 也可以是集合轉化成流,而集合操作是我們在程式中經常使用,并且需要對集合進行周遊等相關業務操作,傳統的for循環周遊,不僅需要等全部for循環完成後才能處理下一步,而且在for循環周遊時,隻能使用一個核,通過将集合轉化成流(通過實際觀察,集合轉成流本質上是new一個新對象的所需的時間耗時極少),轉成流後,就可以充分利用流式處理的特點,顯著的提高程式的響應速度,特别對于大資料業務耗時久的集合操作可以提高十倍甚至百倍的響應速度。