1. 程式人生 > >Java 8 流式處理提高程式響應

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

Java8背景介紹:

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

為什麼使用Java 8?

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

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


上述流圖中,你可以把幾個基礎操作連結起來,來表達複雜的資料處理流水線(在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"));
}

結論分析:

一個統計的表格如下:


上述效能中橫座標是測試的資料集個數,縱座標是耗費的毫秒數。所有資料都是迴圈20次求平均值。

可以看到在資料集小於四百多萬時,Java 8 並行流效能稍微好於Java 8序列流,好於非Java 8,d當大於六百多萬時,反而仍是非Java 8 效能最好。

原因分析:

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

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

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

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

得到的統計資料表如下:

這裡非Java 8的效能和Java8序列的效能幾乎一致,所以重合。

而Java 8 並行的效能已經遠遠好於其他兩種。

原因分析:為什麼Java8序列的效能與非java的效能一致,因為,這裡的流的中間操作只有一個,因此沒有利用流的特性(至少需要兩個),而Java 8的並行流,在處理第一個中間操作開啟多執行緒處理,因此能夠顯著提高效能,其處理耗時縮短10倍以上。

總結:

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

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